Scope of subroutine parameters in FORTRAN 77 - fortran

I have a subroutine that declares i and passes it to other small subroutines. Within those small subroutines there are declared other variables with the same name, i.e i, and it is internally used. Once out of the small subroutines, one would expect to have the same i value that was originally passed, however that is not the real situation and i contains the value of the last parameter assigned within the small subroutines. Here is a brief example:
subroutine smallSub1(i)
integer i,start,end
start=i
end = start +10
do i=start, end
write(*,*) i
enddo
end subroutine smallSub1
subroutine caller
integer i
i = 1
call smallSub1(i)
write(*,*) i
end subroutine caller
My question is, how could I avoid this behavior in F77?
My concern here is: think about a situation where a subroutine is a black box, and you just need to pass an integer, but you do not want that integer value to change from the smallSub1 subroutine. The variables start and end would depend on the value of i however they should not replace the original value of i out of smallSub1

As noted in the other answer, Fortran typically passes by reference and when that is not possible it does things like copy-in/copy-out. As said succinctly by others in comments, if you don't want to change i then don't change i.
In your subroutine smallSub1, i is being used as a loop iteration variable and and you don't want its value changes visible to the caller. The reason changing i is visible to caller is because i isn't a local variable, it is a dummy argument. The solution is call the loop variable and the dummy argument by different names. One such solution is:
subroutine smallSub1(i_from_caller)
integer i,i_from_caller,start,end
start = i_from_caller
end = start +10
do i=start, end
write (*,*) i
end do
end subroutine smallSub1
In this case the dummy argument has been renamed to i_from_caller and is used to initialize start. Now the loop variable i is truly local to the subroutine (as it is no longer the name of a dummy argument) and changing i here won't change i in caller
To help avoid this kind of behavior you can give the compiler hints that dummy arguments are for input, output or both. If you had declared i as integer, intent(in) :: i in smallSub1 in your original example, the compiler would complain:
do i=start, end
1
Error: Dummy argument ā€˜iā€™ with INTENT(IN) in variable definition context (iterator variable) at (1)
making you aware that you are making unwanted changes to a dummy argument.

In the code example given there are two variables i: one in each subroutine. In subroutine caller the i is a local variable, and in the subroutine smallSub1 it is a dummy argument.
When you have call smallSub1(i) you are associating the two i variables with each other through argument association. In this simple case, then, any change to i in smallSub1 affects the i in caller. There is how argument association works here.
Traditionally one did have black-boxes where an argument is changed in the subroutine when that wasn't desired. Where it is used as a work-space, for example. In that case, one would do something like
inew = i
call smallSub1(inew)
... continue using i
In this case, however, one can easily (I imagine) change the subroutine. Introduce an extra local variable:
subroutine smallSub1(i)
integer i ! Dummy argument - we don't want to change it
integer start,end
integer j ! Local variable - we're quite free to change it
! In general, we'd have j=i at the start and use that instead
start=i
end = start +10
do j=start, end
write(*,*) j
enddo
end subroutine smallSub1
And with modern Fortran one even has the value attribute, which can be applied to the dummy argument allowing one to change it without impact on the actual argument.

from https://gcc.gnu.org/onlinedocs/gfortran/Argument-passing-conventions.html
Fortran passes most arguments by reference
Therefore, if you don't want the variable to change, don't change it in the subroutine. You are allowed to rename the variable smallSub1(i) to smallSub1(j)
you don't want to change the entire function.

Related

Fortran: intent(out) and assumed-size arguments

Suppose an dummy argument is modified in a subroutine, and the subroutine doesn't care about its initial value. For a scalar of the standard numerical types (no components), intent(out) would be correct, right?
Now, if the dummy argument is allocatable and the subroutine depends on its allocation status, or if it has components that are not modified by the subroutine but should maintain their initial values (i.e. not become undefined), or, similarly, if it's an array and not all elements are set in the subroutine... Then the proper intent would be intent(inout).
My question is what happens if the dummy argument is an assumed-size array (dimension(*))? Assuming that not all elements are modified in the subroutine and one wants the other elements to retain their initial values, would intent(out) be appropriate? My reasoning is that the subroutine doesn't "know" the size of the array, so it cannot make the elements undefined as it can with a fixed-size array.
Does the standard say anything about this or are compilers free to "guess" for example that if I set A(23) = 42, then all elements A(1:22) can be made undefined (or NaN, or garbage...)?
Note: When I say "retain their initial values", I mean the values in the actual argument outside the subroutine. The subroutine itself doesn't care about the values of these elements, it never reads them or writes them.
Another question looks at what "becomes undefined" means in terms of the dummy argument. However, exactly the same aspects apply to the actual argument with which the dummy argument is associated: it becomes undefined.
The Fortran standard itself gives a note on this aspect "retaining" untouched values (Fortran 2018, 8.5.10, Note 4):
INTENT (OUT) means that the value of the argument after invoking the procedure is entirely the result of executing that procedure. If an argument might not be redefined and it is desired to have the argument retain its value in that case, INTENT (OUT) cannot be used because it would cause the argument to become undefined
That note goes on further to consider whether intent(inout) would be appropriate for when the procedure doesn't care about values:
however, INTENT (INOUT) can be used, even if there is no explicit reference to the value of the dummy argument.
That the array is assumed-size plays no part in the undefinition of the actual and dummy arguments. Again, to stress from my answer to the other question: "undefined" doesn't mean the values are changed. As the compiler has no required action to perform to undefine values, it doesn't need to work out which values to undefine: with an assumed-size dummy argument the procedure doesn't know how large it is, but this doesn't excuse the compiler from "undefining" it, because there's nothing to excuse.
You may well see that the "undefined" parts of the assumed-size array remain exactly the same, but intent(inout) (or no specified intent) remains the correct choice for a compliant Fortran program. With copy-in/copy-out mechanics with intent(out), for example, the compiler wouldn't be obliged to ensure the copy is defined and that junk isn't copied back.
Finally, yes a compiler (perhaps in the hope of being a good debugging compiler) may change values which have become undefined. If the procedure references the n-th element of an array, it's allowed to assume that there are n-1 elements before it and set them as it chooses, for an intent(out) dummy, but not an intent(inout) dummy. (If you access A(n)then the compiler is allowed to assume that that element, and all from the declared lower bound, exist.)

Why do I always get the same result when using function and contains in fortran

I'm trying to do a basic calculation by calling a function using contains
Program main
implicit none
integer*8 Nmax,i
Parameter (Nmax=5)
real*8 x, f(Nmax), n
do i=1, Nmax
n=i
f=func(n,Nmax)
write(*,*) f(i)
end do
Contains
real*8 function func(x,Nmax)
integer*8 Nmax,i
real*8 x, f(Nmax)
do i=1, Nmax-1
f(i)=i**2d0-4d0*i-7d0
end do
end function
end program main
I get this result:
-9.255963134931783E+061
-9.255963134931783E+061
-9.255963134931783E+061
-9.255963134931783E+061
-9.255963134931783E+061
I think I'm making the wrong variable definitions. Thank you for your help.
There are multiple problems with your program.
First, you probably meant to write:
f(i)=func(n,Nmax)
in the main program. Without the subscript you assign the same value to each element of the array. You might think that explains the results, but it doesn't as you'd still see what you expect.
Another problem is highlighted by the following warning I get when I compile your code with Intel Fortran:
t.f90(14): warning #6178: The return value of this FUNCTION has not been defined. [FUNC]
real*8 function func(x,Nmax)
-------------------^
You never assign the value of func, so you get whatever garbage happens to be in the return register.
The function you have isn't really what you want, either. You probably want one that computes and returns a scalar (single) value and hence there is no need for an array inside func.
A third problem is that func is ignoring the n argument (which, contrary to convention, you have declared as a real.)
If you want a loop in the main program, have the function compute and return a single result based on the argument passed to it. There is no need to pass both the loop index and nmax each time. Other options, slightly more advanced, would be to keep the array assignment in the main program but do away with the loop there and either have the function return an array or make the function ELEMENTAL. I will leave it as an exercise for you once you figure out what you really intend here.
Lastly, I would discourage you from using nonstandard syntax such as "real*8". Please learn about KIND specifiers and the SELECTED_REAL_KIND intrinsic function.

fortran result variable Not initiallized

I met a surprsing problem about loacal variable initializing.
I got following function to calculate gammar
function gammar(z) result(gz)
implicit none
real(8),intent(out)::gz
real(8)::z,t,low,up
real(8),parameter::increment=1.0
real(8),parameter::lower_t=0.0,upper_t=10.0
integer(4)::i,n
!gz=0.0
n=(upper_t-lower_t)/increment
do i=1,n
low=lower_t+(i-1)*increment
up=lower_t+(i)*increment
gz=gz+(f(z,low)+f(z,up))*increment/2.0
end do
end function gammar
Then I call this function in main program like
df=9.0
t=0.0
write(*,*) gammar((df+1.0)/2.0)/sqrt(pi*df)/gammar(df/2.0)
I got wrong answer!! 0.126
I found the reason was after gammar((df+1.0)/2.0) was calculated, the local variable gz was not set to 0.
Hence ,when calculate gammar(df/2.0), the gz still retained old value 24. Eventually,gammar(df/2.0) got wrong answer 34..
If I add gz=0.0 in the gammar function, this problem was fixed.
This is really surprising. Why the local gz was not initiallized to zero when the gammar called every time?
Thanks a lot
Regards
Ke
Unless you have a statement to initialize a local variable in a procedure, such as the gz = 0 that you have commented out, those local variables are not initialized when the procedure is invoked. Their values are undefined. They could have a value left from a previous invocation, or some random value.
If you use full warning options of the compiler, it will likely tell you of this problem. gfortran warned of an uninitialized variable at compile time. ifort detected the problem at run time.
Another initialization method is with a declaration. That still doesn't repeat the initialization of additional invocations of the procedure. If you initialize a local variable in a procedure with a declaration, such as integer :: count = 0, that initialization is only done on the first invocation on the procedure. But ... the variable remains defined and on the next invocation will retain that value that it had when the previous invocation exited.
P.S. real(8) is not a portable way to obtain double precision reals. The language standard does not specify specific numeric values for kinds ... compilers are free to whatever value they wish. Most compilers use the number of bytes, but use other numbering methods. It is better to use selected_real_kind or the ISO_FORTRAN_ENV and (for double precision) real64. See quad precision in gfortran
P.P.S. Trying this code with gfortran, that compiler points out another problem with gz:
function gammar(z) result(gz)
1
Error: Symbol at (1) is not a DUMMY variable
So remove the intent(out) in the declaration.

Fortran: Variables changing on their own

I'm having a problem of variables getting over-written for I don't know what reason. I've posted a chunk of the code below so you can see how things are declared. The variables strain,Qi,Qf,Qd,tel and Gc are passed into the subroutine and used to calculate ssgrad,strn0,strss0.
My problem is that tel and Gc are passed into the subroutine OK but are for some reason change value during this chunk of code.
Using print statements I've found that the problem first occurs during the 2nd do loop. When I set strss0 to 0, Gc and tel change value from both being equal to 1, to seemingly random numbers: tel=11.52822 Gc=-8.789086 (Just shown for the sake of example)
Each time I run the code they are set to the same values though.
Just to let you know, this subroutine interfaces with a commercial finite element package.
Many thanks in advance for any help with this
subroutine initcalcs(strain,Qi,Qf,Qd,tel,Gc,ssgrad,strn0,strss0)
implicit none
integer :: i,j
real*8:: nstrn0,nstrs0,strn0,strnf,varsq,normvar,lmbda0,lmbdaf,
# ssgrad,t0,tt,tel,nstrnf,nstrsf,Gc
real*8, dimension(3) :: strain,stran0,stranf,strss0,strssf,var
real*8, dimension(3,3) :: Qd,Qi,Qf
lmbda0=1.0d0
nstrn0=0.0d0
do i=1,3
stran0(i)=0.0d0
stran0(i)=strain(i)*lmbda0
nstrn0=nstrn0+stran0(i)**2
end do
nstrn0=dsqrt(nstrn0)
do i=1,3
strss0(i)=0.0d0
end do
In Fortran, there are two common causes of the corruption of memory values. One is a subscript error, where you assign to an array element using an incorrect subscript value. This writes to a memory location outside of the array. The other is a disagreement between the arguments in the call to a procedure (subroutine or function) and the dummy arguments of the procedure. Either can cause problems to appear at source code locations different from the actual cause. Suggestions: inspect your code for these problems. Turn on stringent warning and error checking options of your compiler. The use of Fortran >=90 and modules gives Fortran much better ability to automatically find argument consistency problems. You could monitor the memory locations with a debugger and see what it modifying it.
I concur with M. S. B.: turn on stringent warnings and error checking and verify the subroutine calls are passing arguments that have the same type and shape (array dimensions) as the subroutine expects.
The colons in the variable declaration syntax imply this is Fortran90 or later. If that's the case, I strongly suggest using the INTENT modifier to specify whether arguments are intended to be read-only.
For example, let's assume that of the arguments passed to this routine, strain, Qi, Qf, Qd, tel, and Gc are read-only input and the arguments are ssgrad, strn0, and strss0 are returned as output; that is, whatever value they have is overwritten by this routine.
The variable declarations for the arguments would change to:
real*8, dimension(3), intent(in) :: strain
real*8, dimension(3,3), intent(in) :: Qi, Qf, Qd
real*8, intent(in) :: tel, Gc
real*8, intent(out) :: strn0, ssgrad
real*8, dimension(3), intent(out) :: strss0
The INTENT keyword is an addition to Fortran 90 which allows the user to specify which arguments are read-only (INTENT(IN)), initialized but which may be modified within the routine (INTENT(INOUT)) and which are treated as uninitialized and will be set within the routine (INTENT(OUT)).
If INTENT is not specified, it is defaults to INOUT which is consistent with FORTRAN 77 (Note that there are minor differences between INTENT(INOUT) and INTENT not being specified but they aren't relevant in this example).
A good compiler will throw an error if a routine tries to assign a value to a variable declared INTENT(IN) and will at least throw a warning if a variable declared INTENT(OUT) doesn't get assigned a value.
If possible, set INTENT(IN) on all the variables which are supposed to be read-only. This may not be possible, depending on how those variables are passed to other routines. If INTENT isn't specified on arguments to routines called within this routine, it will default to INOUT. If you pass an INTENT(IN) variable as an INTENT(INOUT) argument, the compiler will throw an error. If this is happening in code you control, you have have to specify INTENT in a number of routines. This may or may not be desirable depending on whether you want to generally improve your code or just fix this one problem really quickly.
I'm assuming some of these variables are passed to external routines in the finite element package which I 'm guessing is linked to your code rather than compiled; I'm not sure how compile-time intent checking is handled in that case.

Fortran SAVE statement

I've read about the save statement in the (Intel's) language reference document, but I cannot quite grasp what it does. Could someone explain to me in simple language what it means when the save statement is included in a module ?
In principal when a module goes out-of-scope, the variables of that module become undefined -- unless they are declared with the SAVE attribute, or a SAVE statement is used. "Undefined" means that you are not allowed to rely on the variable having the previous value if you again use the module -- it might have the previous value when you re-access the module, or it might not -- there is no guarantee. But many compilers don't do this for module variables -- the variables probably retain their values -- it isn't worth the effort for the compiler to figure out whether a module remains in scope or not and probably module variables are treated as global variables -- but don't rely on that! To be safe, either use "save" or "use" the module from the main program so that it never goes out of scope.
"save" is also important in procedures, to store "state" across invocations of the subroutine or function (as written by #ire_and_curses) -- "first invocation" initializations, counters, etc.
subroutine my_sub (y)
integer :: var
integer, save :: counter = 0
logical, save :: FirstCall = .TRUE.
counter = counter + 1
write (*, *) counter
if (FirstCall) then
FirstCall = .FALSE.
....
end if
var = ....
etc.
In this code fragment, "counter" will report the number of invocations of subroutine x. Though actually in Fortran >=90 one can omit the "save" because the initialization in the declaration implies "save".
In contrast to the module case, with modern compilers, without the save attribute or initialization-on-a-declaration, it is normal for local variables of procedures to lose their values across invocations. So if you attempt to use "var" on an later call before redefining it in that call, the value is undefined and probably won't be the value calculated on a previous invocation of the procedure.
This is different from the behavior of many FORTRAN 77 compilers, some of which retained the values of all local variables, even though this wasn't required by the language standard. Some old programs were written relying on this non-standard behavior -- these programs will fail on the newer compilers. Many compilers have an option to use the non-standard behavior and "save" all local variables.
LATER EDIT: update with a code example that shows incorrect usage of a local variable that should have the save attribute but doesn't:
module subs
contains
subroutine asub (i, control)
implicit none
integer, intent (in) :: i
logical, intent (in) :: control
integer, save :: j = 0
integer :: k
j = j + i
if ( control ) k = 0
k = k + i
write (*, *) 'i, j, k=', i, j, k
end subroutine asub
end module subs
program test_saves
use subs
implicit none
call asub ( 3, .TRUE. )
call asub ( 4, .FALSE. )
end program test_saves
Local variable k of the subroutine is intentionally misused -- in this program it is initialized in the first call since control is TRUE, but on the second call control is FALSE, so k is not redefined. But without the save attribute k is undefined, so the using its value is illegal.
Compiling the program with gfortran, I found that k retained its value anyway:
i, j, k= 3 3 3
i, j, k= 4 7 7
Compiling the program with ifort and aggressive optimization options, k lost its value:
i, j, k= 3 3 3
i, j, k= 4 7 4
Using ifort with debugging options, the problems was detected at runtime!
i, j, k= 3 3 3
forrtl: severe (193): Run-Time Check Failure. The variable 'subs_mp_asub_$K' is being used without being defined
Normally, local variables go out of scope once execution leaves the current procedure, and so have no 'memory' of their value on previous invocations. SAVE is a way of specifying that a variable in a procedure should maintain its value from one call to the next. It's useful when you want to store state in a procedure, for example to keep a running total or maintain a variable's configuration.
There's a good explanation here, with an example.
A short explanation could be: the attribute save says that the value of a variable must be preserved across different calls to the same subroutine/function. Otherwise normally when you return from a subroutine/function, "local" variables lose their values since the memory where those vars were stored is released. It is like static in C, if you know this language.