Fortran How does Fortran know the Inputs and Outputs of Subroutines

Click For Summary
Fortran determines the inputs and outputs of subroutines primarily through a call-by-reference mechanism, where the addresses of variables are passed. Variables used in a subroutine must be defined in both the main program and the subroutine, but they can have different names. A subroutine can modify input variables, and if they are passed by reference, those changes will reflect in the main program. Additionally, a subroutine cannot access variables defined in the main program unless they are explicitly passed as arguments or shared through COMMON blocks, which can lead to unintended side effects. Understanding these principles is crucial for effectively working with Fortran code and avoiding errors during porting or debugging.
ecastro
Messages
249
Reaction score
8
I have trouble understanding how Fortran knows the inputs and outputs of a subroutine. I am studying a code written in Fortran, but I do not have a compiler, so I cannot check how it works.

Here is my problem; for example, I have a subroutine,

subroutine test(x, y, z, a, b, c)

The variables x, y, and z have values in the main program and are needed in the subroutine, while a, b, and c are the supposed outputs. What troubles me are:
1. Should all six variables be defined in the main program and in the subroutine (defined to be real, integer, complex, etc.)?
2. Is it alright if I define (defined to be real, integer, complex, etc.) a variable in the subroutine and not in the main program? How about vice versa? What are the consequences of this action?
3. If x, y, and z somehow receive a new value in the subroutine, will the previous values be replaced in the main program (x, y, and z become part of the output)?
4. If there's a variable named d in the subroutine, will I be able to call it in the main program even though it's not part of the output?

Thank you in advance.
 
Technology news on Phys.org
ecastro said:
1. Should all six variables be defined in the main program and in the subroutine (defined to be real, integer, complex, etc.)?
Yes, but they can have different names - the names are symbolic and serve as references
The fortran parameter passing mechanism is mainly by reference: the address of a variable is passed to the subroutine.
2. Is it alright if I define (defined to be real, integer, complex, etc.) a variable in the subroutine and not in the main program? How about vice versa? What are the consequences of this action?
yes. no consequences. In some compilers you can not be sure a variable in a subroutine is static (has the same value in a call as it had been given in a preceding call)
3. If x, y, and z somehow receive a new value in the subroutine, will the previous values be replaced in the main program (x, y, and z become part of the output)?
A good environment will protect you against such an access violation, but I'm not 100% sure.
[edit] Ignore. Confusion with newer languages on my part. Vanadium corrects me in post #2 and Anorlunda elucidates in #3.
F90 has some features in this direction
4. If there's a variable named d in the subroutine, will I be able to call it in the main program even though it's not part of the output?
No. Other routines (including the main program) do not know where to find d
 
Last edited:
ecastro said:
3. If x, y, and z somehow receive a new value in the subroutine, will the previous values be replaced in the main program (x, y, and z become part of the output)?

Yes. This is a feature of the language.
 
In older FORTRANs, all arguments are by-reference and inherently input and/or output. That is very simple, but it lacks safeguards against errors.

Consider this routine
Fortran:
SUBROUTINE BAZFAX(X,Y,Z)
INTEGER X,Y,Z
Z=0
X=X/Y
RETURN
END

It appears that:
X is input and output
Y is input
Z is output
It is also possible to use IF statements to make it conditional whether an argument is used as input, output or both.

Now think what might happen with these calls
Fortran:
CALL BAZFAZ(1,2,3)
CALL BAZFAZ(X*2,Y*3,SQRT(Z))
CALL BAZFAX(X,X,X)

The first two calls have no arguments suitable for outputs.
The last call will crash with divide by zero because in the SUBROUTINE, Y and Z refer to the same memory address.

The point is that the caller must know how the subroutine is written and which arguments are used as outputs, inputs, or both. You can get into all sorts of trouble if you do it wrong. That is why other languages include safeguards to prohibit some of these kinds of errors.

EDIT: I neglected to mention that you can also declare the types of arguments to be different than the types supplied in the CALL. Even incompatible types. You can also have an inconsistent count of the number of arguments between the caller and the routine.

All these things are consequences of a very simple model. Arguments are merely addresses of a memory region to use for whatever you want. FORTRAN compilers compile programs and subprograms separately, without any prior knowledge of the other, nor any macro-data left behind about the signature. That was the optimum strategy when computer resources were severely limited, and when compiling programs was nearly as difficult as executing them.
 
Last edited:
Newer versions of Fortran (Fortran 90?) use syntax in the declaration of subroutine and function parameters that indicates whether a parameter should be considered input, output, or both.
Fortran:
subroutine square_cube(i, isquare, icube)
  integer, intent(in)  :: i             ! input
  integer, intent(out) :: isquare, icube ! output
  isquare = i**2
  icube   = i**3
end subroutine square_cube

program xx
  implicit none
  integer :: i, isq, icub
  i = 4
  call square_cube(i, isq, icub)
  print*,"i,i^2,i^3=",i,isq,icub
end program xx
 
ecastro said:
I am studying a code written in Fortran
Ha, I missed that in first reading. What's the goal: reverse engineering for technology, porting to a newer environment, ?
[edit] and your source code dates from when, approximately ? Any knowlegde about the environment it was used in (IBM, DEC, CDC... ) ?
 
Thank you for your comments and precautions on the subroutines. I have another question, can a subroutine access a variable that is not included as an input but defined in the main program?

BvU said:
Ha, I missed that in first reading. What's the goal: reverse engineering for technology, porting to a newer environment, ?
[edit] and your source code dates from when, approximately ? Any knowlegde about the environment it was used in (IBM, DEC, CDC... ) ?

I think more like porting to a newer environment, a checking if the code I am writing is correct. The code was last updated May 2015, but I have no knowledge of its environment.
 
2015 is very recent for a Fortran IV programmer :smile:A subroutine does not have access to the adresses (variables) the main program uses (unless they are passed to the subroutine as arguments).

However:
The issue I'm about to adress now requires distinction between Fortran 90 and predecessors like Fortran 77.
One of the gizmos in older Fortran that has both been very useful AND a horrifiying source of errors is the COMMON block. If that doesn't occur in your code, then you're lucky; if it does, then that's a way for the routines (in which such a common block is declared) to all access the same variables.

In fortran 90 that role is taken over (in a more structured way) by modules

ecastro said:
porting to a newer environment
I hope you know what you are doing. Porting is error-prone and you may well end up with a program that is less efficient. You sure the investment in a compiler and learning some mixed-language procedure calling isn't much more effective and time-saving ?
 
Last edited:
ecastro said:
I have another question, can a subroutine access a variable that is not included as an input but defined in the main program?
As BvU notes, this is possible to do using a COMMON block. However, it's considered very bad practice, in any programming language, for a function or subroutine to access variables other than its own local variables or those passed as parameters.
 
  • #10
Dear Mark, OP has indicated he has to deal with whatever is in there already !

Interesting case, isn't it ?
 
  • #11
ecastro said:
Thank you for your comments and precautions on the subroutines. I have another question, can a subroutine access a variable that is not included as an input but defined in the main program?
If one is a tricky sort with either faith or inside knowledge about the storage layout in the main program, one could declare a parameter in a subroutine as an array and proceed to use array references to access variables not appearing in the parameter list.

e.g.
Code:
       integer i, j, k
       call sub ( j )
       end
       [...]
       subroutine sub ( m )
       integer m(3)
       m(0) = 1        ! Modifies i (and may require you to have compiled the subroutine with array bounds checking disabled)
       m(1) = 2        ! Modifies j
       m(2) = 3        ! Modifies k
       end
This is, of course, very bad practice. In addition to violating the expectations of all sane code readers, an optimizing compiler could render the approach useless, making memory allocation order unpredictable, moving variables into registers or optimizing them away altogether. One of the virtues (and one of the problems) associated with using common blocks is that memory allocation order is constrained and the compiler is not permitted to make such optimizations.

With DEC Fortran-77 (and presumably others), one could make games like this somewhat more kosher by using directives like %loc(variablename) to obtain the address of a variable. The resulting address could then be passed to a subroutine. The compiler was smart enough to use the appearance of %loc(x) as a hint that x was volatile and subject to modification behind the compiler's back -- so that optimizations for that variable would not be attempted.
 
  • Like
Likes BvU
  • #12
Unfortunately, there are multiple common blocks in the subroutine. In my case, there is this:

common /common_name/ x, y, z

This is in both the main program and in the subroutine. If I understand correctly on how these work, you can access the variables if 'common_name' was called inside the subroutine. Does this mean if the variables 'x', 'y', and 'z' changed values inside the subroutine, they will look like outputs? That is, if these variables were called after the subroutine, they will have new values, right?

BvU said:
I hope you know what you are doing. Porting is error-prone and you may well end up with a program that is less efficient. You sure the investment in a compiler and learning some mixed-language procedure calling isn't much more effective and time-saving ?

I am almost done with the code, the problem is my output from my code is not what I expect.

@jbriggs444 I don't understand your example. There is a subroutine named 'sub' which has a single input 'm' (from the main program, the input is 'j'). How does it modify 'i' and 'k'? Isn't the output of your subroutine just modifies the input?
 
  • #13
ecastro said:
I am almost done with the code, the problem is my output from my code is not what I expect.

BvU said:
I hope you know what you are doing. Porting is error-prone and you may well end up with a program that is less efficient.
Or one that produces output that you don't expect. As BvU said, porting is error-prone...

ecastro said:
@jbriggs444 I don't understand your example. There is a subroutine named 'sub' which has a single input 'm' (from the main program, the input is 'j'). How does it modify 'i' and 'k'? Isn't the output of your subroutine just modifies the input?
Fortran is a call-by-reference language. This means that any subroutine or function can modify its parameters. In contrast, C and other languages that stem from C (such as C++ and others) are call-by-value languages, whereby a function cannot modify its parameters.

In call-by-reference what is actually passed in a function or subroutine call is the address of the parameter. "call sub(j)" is passing the address of j. In the sub subroutine, the parameter is declared as a three element array. Fortran arrays have indexes that normally start at 1, so the value for the parameter j would normally be at m(1). By accessing m(0), the subroutine has access to i (in the main program), and m(1) provides access to j (in the main program), and m(2) provides access to k (in the main program). The subroutine doesn't know about the identifiers i, j, and k, but it has access to them via the array m.
 
  • #14
Mark44 said:
In call-by-reference what is actually passed in a function or subroutine call is the address of the parameter. "call sub(j)" is passing the address of j. In the sub subroutine, the parameter is declared as a three element array. Fortran arrays have indexes that normally start at 1, so the value for the parameter j would normally be at m(1). By accessing m(0), the subroutine has access to i (in the main program), and m(1) provides access to j (in the main program), and m(2) provides access to k (in the main program). The subroutine doesn't know about the identifiers i, j, and k, but it has access to them via the array m.

This is problematic... So, it doesn't just change the input 'j' (presumably a single-valued integer value) to an array?
 
  • #15
ecastro said:
This is problematic... So, it doesn't just change the input 'j' (presumably a single-valued integer value) to an array?
No. The main program (the caller of sub()) knows about i, j, and k. It (main) doesn't know about m. The subroutine, sub(), knows about m, but doesn't know about i, j, or k. By passing addresses, which is how it works in Fortran and other call-by-reference languages, a subroutine doesn't have to know the names of variables, but can still have read or write access to them.

The caller (main program) sends the address of j as the argument to sub() in the call. The subroutine treats the address that is sent as an array with three elements: m(1), m(2), and m(3).

The value stored in the address that m(1) represernts is the value stored in j. Likewise, the value stored in the address that m(2) represents is the value stored in k.m(0) represents the memory address that comes before (i.e., lower in memory) has the value stored in i. This is my understanding of jbriggs888's example, which I'm pretty sure is what he was driving at.
 
  • Like
Likes jbriggs444
  • #16
ecastro said:
Unfortunately, there are multiple common blocks in the subroutine. In my case, there is this:

common /common_name/ x, y, z

This is in both the main program and in the subroutine. If I understand correctly on how these work, you can access the variables if 'common_name' was called inside the subroutine. Does this mean if the variables 'x', 'y', and 'z' changed values inside the subroutine, they will look like outputs? That is, if these variables were called after the subroutine, they will have new values, right?
I see. These variable addresses are known in all routines that have the common declared. Note I use the word address -- the variable names may differ from routine to routine. Not good practice, but it happens.

Your understanding is correct. Such variables 'look like' outputs and also like inputs. They are common :rolleyes: (their 'scope' spans the routines that declare the common block). You have to be careful to check which routine is responsible for e,g, initializing them.

ecastro said:
I am almost done with the code, the problem is my output from my code is not what I expect.
Painted yourself in a corner. If it's a big program you could spend a long time debugging. If you are unlucky, not just your code but the original code as well. Do you have output from the old program to compare with ? What's the language you are porting to ? Can you restrict your debugging to a limited set of source code and give concrete examples ? Is it all very confidential ? Did you use automatic code converting ?
(there's a lot of very, very experienced fortranners about who'd love to help you by digging into this out of pure sentimentality :smile:)
ecastro said:
@jbriggs444 I don't understand your example. There is a subroutine named 'sub' which has a single input 'm' (from the main program, the input is 'j'). How does it modify 'i' and 'k'? Isn't the output of your subroutine just modifies the input?
JB example is malignant. Be optimistic (you must be) and give the original programmers the benefit of the doubt.
 
  • Like
Likes jbriggs444
  • #17
ecastro said:
Unfortunately, there are multiple common blocks in the subroutine. In my case, there is this:

common /common_name/ x, y, z

This is in both the main program and in the subroutine. If I understand correctly on how these work, you can access the variables if 'common_name' was called inside the subroutine. Does this mean if the variables 'x', 'y', and 'z' changed values inside the subroutine, they will look like outputs? That is, if these variables were called after the subroutine, they will have new values, right?
Yes.
I am almost done with the code, the problem is my output from my code is not what I expect.

@jbriggs444 I don't understand your example. There is a subroutine named 'sub' which has a single input 'm' (from the main program, the input is 'j'). How does it modify 'i' and 'k'? Isn't the output of your subroutine just modifies the input?
It thinks that m is an array and it knows the starting address of the array. So it can modify values from that address and higher when the index of m increases. (The case of m(0) => i is special and only if certain compiler options have been set. Usually the lowest index of a FORTRAN array is 1.)
 
  • #18
FactChecker said:
It thinks that m is an array and it knows the starting address of the array. So it can modify values from that address and higher when the index of m increases. (The case of m(0) => i is special and only if certain compiler options have been set. Usually the lowest index of a FORTRAN array is 1.)
Yes.

For the compiler that I used to use in the 1980's (VAX Fortran on VMS) one would have needed to compile with options like:

Code:
    $ fortran test /check=nobounds
    $ link test
    $ run test

By default, the compiler would build in run time array bounds checking so that an attempt to access array element zero (one cell before the array started at element one) would throw an error at run time. I no longer remember if it was smart enough to throw the error at compile time when the index was a knowable compile time constant.

While one could declare an array as, for instance,

Code:
       subroutine sub ( m )
       int m(0:2)
       m(0) = 1
       m(1) = 2
       m(2) = 3

this would not accomplish the goal of accessing a memory location prior to the start of the array. The parameter passed in the argument list is interpreted as the address of the first array element based on the way the array is declared in the subroutine. As declared above, the first element of m is at index zero. The memory location one cell prior would be m(-1) and would still trigger an array bounds error, by default, if access were attempted.
 
  • #19
ecastro said:
Unfortunately, there are multiple common blocks in the subroutine. In my case, there is this:

common /common_name/ x, y, z

It is worse than you imagine.

  1. First, FORTRAN also had a feature called EQUIVALENCE. That allowed the programmer to create an indefinite number of aliases for the same memory locations, either COMMON memory or stack memory.
  2. Second, it was regular practice to use COMMON in ways to mimic the use of "heap" storage in other languages. In a language like C, one could use x=malloc(1000) to reserve 1000 bytes of memory, and free(x) to release the memory block. In FORTRAN we might say COMON/BAZFAZ/I,J,K in one subprogram, COMMON/BAZFAZ/X(3) in another subprogram, and COMMON/BAZFAZ/A,B(2) in a third subprogram. In all three cases, the same memory block is referenced. That allowed memory to be reused for different purposes in different phases of the program, just like heap storage in other languages.
  3. Third, in the days when FORTRAN had no INCLUDE feature, and before the time when text editors had copy/paste, or when we could store source code in files, the COMMON declaration in each subprogram had to be retyped by hand. A single character typo, could be enough to misalign the COMMON declarations leading to bugs that were extraordinarily hard to find. The typos did not cause compilation errors. More than once I helped hapless programmers proof read their COMMON declarations with great tedium, character-by-character subprogram-by-subprogram. With punch cards, you could reliably duplicate cards one-at-a-time but making 200 duplicates of a 100 card long COMMON declaration was its own kind of tedium and still gave lots of opportunities for errors.
  4. There was one context where "global COMMON" was not evil. I worked on real time simulators where programs executed 10 to 50 times per second, and could not be slowed down or halted temporarily for debugging purposes. We made 100% of the variables global. Even local temps were made global. That made it possible to make real time debuggers that could capture data for debug purposes from any part of any program without changing the real time performance. It worked remarkably well, streamlining debugging and interprogram communications. Imagine just pointing a D-A converter to a memory address, and then to an oscilloscope or a chart recorder without changing any program code. The evil of global data comes when programmers forget which data is local and which is global, but if 100% is global, there is no confusion.
The OP faced with the task of maintaining a very old program is in a bad spot. Because of call by ref, COMMON and EQUIVALENCE, you can not dependably break down the task into program modules. You may need to examine every line of code in every subprogram for possible hidden interactions with other subprograms.

The SUBROUTINE and FUNCTION features in FORTRAN provided code reuse and separate compilation, but they did not provide modularity.

Thinking back on the constraints we lived with, it sounds impossible that we could achieve what we did, but we did make things work correctly.
 
  • #20
anorlunda said:
Thinking back on the constraints we lived with, it sounds impossible that we could achieve what we did, but we did make things work correctly.
That being said, there were many times when common blocks were the only realistic way to get things done.
One further complication was that the same position in a common block could be used under different names in different subroutines. And a subroutine that needed only the 58'th and 68'th variables might get them by: common /xxx/ dummy1(57), var1, dummy2(9), var2. (Or something like that. My memory is vague on it.)
I had to write some programs to track down and cross-reference all the usage of common block data elements in large programs.
 
Last edited:
  • #21
BvU said:
Painted yourself in a corner. If it's a big program you could spend a long time debugging. If you are unlucky, not just your code but the original code as well. Do you have output from the old program to compare with ? What's the language you are porting to ? Can you restrict your debugging to a limited set of source code and give concrete examples ? Is it all very confidential ? Did you use automatic code converting ?

It is indeed a long program. The code is from NASA, so I trust that it works. The code is not that confidential, it is actually available here: http://6s.ltdri.org/pages/downloads.html, and there is an example output in the user manual. I am learning the physics of the code, and I guess if I were able to replicate the code, I understand it well.

I am actually looking every line of code, and I haven't seen the references of @anorlunda and @FactChecker. As far as I have seen, every time the commons are declared, they have the same set of names and every variable is present, as to how they were declared in the beginning of the main program.

I came asking here because there is a variable, 'roatm', (an array to be specific) that was only defined in the main program, and by following the inputs of the code manual, the array has no values but it is used to calculate a quantity, which returns a not-a-number value.

I am also curious if I understand this method of Fortran correctly,

do 10 i = a, b
if (insert condition) goto 10
[other processes]
10 continue

If the condition is satisfied, the process should jump to the line 10. Then, does this continue to the next iteration, or does it break the iteration?
 
  • #22
ecastro said:
I am also curious if I understand this method of Fortran correctly,

do 10 i = a, b
if (insert condition) goto 10
[other processes]
10 continue

If the condition is satisfied, the process should jump to the line 10. Then, does this continue to the next iteration, or does it break the iteration?
Yes, it continues to the next iteration. Line 10 is considered to be still inside the "do loop". To jump out of the loop it has to goto a line other than 10, outside of the loop
 
  • #23
ecastro said:
so I trust that it works
Dangerous ! But it looks decent, is pretty modern and doesn't look devious (no EQUIVALENCE statements).
ecastro said:
learning the physics of the code, and I guess if I were able to replicate the code, I understand it well
That's rather roundabout (for the physics especially) :smile:
ecastro said:
I came asking here because there is a variable, 'roatm', (an array to be specific) that was only defined in the main program, and by following the inputs of the code manual, the array has no values but it is used to calculate a quantity, which returns a not-a-number value.
roatm(3,20) sits is in common /sixs_disc/ which is declared in DISCOM.f and in INTERP.f, as well as in main.f, mainlutareo.f and SPECINTERP.f

Values are written into it in DISCOM
Fortran:
        roatm(1,l)=rorayl
        roatm(2,l)=romix
        roatm(3,l)=roaero
In other words: a more specific question in post #1 would have given you this answer a long time ago ! But you have learned a lot about Fortran in the mean time, so it's not a waste.
 
  • #24
BvU said:
roatm(3,20) sits is in common /sixs_disc/ which is declared in DISCOM.f and in INTERP.f, as well as in main.f, mainlutareo.f and SPECINTERP.f

Yes, the values of 'roatm' come from the DISCOM subroutine. However, the whole subroutine is under four conditions, two of the first conditions are for entering the subroutine while the last two skips it. It would seem that I misunderstood the process of the do-go-to loop. I thought that if the code were to have a go-to under a do-loop, the whole do-loop breaks, so I had thought that I missed something on the processes of subroutines.

Thank you for all your help, I hope this works now.
 
  • #25
Did the original NASA code generate NaNs, or was that your code?
 
  • #26
ecastro said:
The code is from NASA, so I trust that it works.

Three words. Mars. Climate. Orbiter.
 
  • #27
Story goes: not NASA error, though...
 
  • #28
FactChecker said:
Did the original NASA code generate NaNs, or was that your code?

It was my code that generated NaNs. According to the user manual, the code should generate a numerical value.

Vanadium 50 said:
Three words. Mars. Climate. Orbiter.

Ah, yes. I was reminded that there was a failed mission due to conversion errors. :biggrin: But, I suppose this code is used for the generation of satellite products and is accompanied by published papers.
 
  • #29
Can you post your NaN disaster as a more concrete, traceable casus ?
 
  • #30
Wasted some of my employers time dragging these source files into a DEC compiler (which, yuck, they call an Intel compiler nowadays) and got loads of errors (*), but some of them are pretty serious:
in AKTOOL.f subroutine dakg writes into u(30+) (up to 48) where the calling subroutine alkalbe declares uu(30), a(30)
Doesn't have to be a disaster (if the call isn't executed ever), but sure isn't very robust .

(*) well, 25 of them, and I compiled everything -- and there are clearly versions that probably shouldn't be used, like newMODIS ("subscript out of range: SR(13,...)" -- sr is declared SR (12,1501) ) etc.

I'm not familiar with gfortran, but if you can set compiler switches, you should do a maximum of checking.
 

Similar threads

  • · Replies 8 ·
Replies
8
Views
2K
  • · Replies 20 ·
Replies
20
Views
3K
  • · Replies 3 ·
Replies
3
Views
2K
  • · Replies 25 ·
Replies
25
Views
3K
  • · Replies 8 ·
Replies
8
Views
4K
  • · Replies 4 ·
Replies
4
Views
3K
  • · Replies 5 ·
Replies
5
Views
8K
  • · Replies 2 ·
Replies
2
Views
3K
  • · Replies 10 ·
Replies
10
Views
10K
  • · Replies 5 ·
Replies
5
Views
4K