As you can see, the structure of a subprogram is exactly like the structure of the main program. It really is a little program in and of itself.
The concept of arguments is new however, and requires some explanation.
The variables base
and
exponent
in
power()
are called
argument variables.
Fortran argument variables don't have memory locations of their own, which is why they are also called dummy variables. Rather, They act as aliases for the arguments that are passed to the function by the calling subprogram.
C argument variables do have memory addresses of their own, so they are effectively like any other local variable, except that they are initialized to the value of the argument sent by the calling subprogram.
y = power(3.0d0, x + 5)
y = power(3.0, x + 5);
The first argument variable in a subprogram's argument list represents the first argument from the caller, and so on. For example, the 3.0 above is represented by base and x + 5 is represented by exponent.
Unlike some intrinsic functions and subroutines, the argument types of our own functions and subroutines are not flexible. For example, the sin() function can take real, real(8), complex, or double complex arguments, and will return the same type. Our power function, on the other hand, must get a 32-bit floating point value for the first argument (base) and an integer for the second (exponent).
It is imperative that the data type of the argument in the caller is exactly the same as the data type of the argument variable. If the argument variable is an integer, then the subprogram will interpret the argument as a 32-bit two's complement value, regardless of what type of argument was sent by the caller. For example, consider the following call to power():
y = power(2.0d0, 3.0)
The second argument, 3.0, is a Fortran real, and therefore formatted in binary as a 32-bit IEEE floating point value. Inside the power function, however, this value of 3.0 is represented by the integer argument exponent, and therefore the bits are interpreted as a 32-bit integer value.
The actual binary representation of 3.0 in 32-bit IEEE format is 01000000010000000000000000000000. Interpreted as a 32-bit integer value, this binary pattern is 222 + 230, or 1,077,936,128. Could this throw off the results of your program slightly?
To avoid problems with type matching, we should define an interface for each subprogram in Fortran or a prototype in C. A subprogram interface or prototype defines how to interact with the subprogram (how many arguments it takes, what data type each argument must be, and what data type of value it returns). It allows the compiler to validate all arguments passed to a subprogram and issue an error message when there is a mismatch. It also shows what type of value a function returns so that the compiler can use it appropriately.
The reason this is necessary is that C and Fortran use a one pass compiler, which reads through the source code from top to bottom only once. Hence, if a subprogram is called earlier in the program than it is defined, the compiler will not yet have seen the definition and will therefore not know what types of arguments it takes or what type of value it returns. By providing an interface or prototype before the first call to a subprogram, we give the compiler all the information it needs to verify the call to the subprogram and issue errors or warnings if necessary.
An interface or prototype is essentially an exoskeleton of a subprogram. It is identical to the subprogram with the local variables and statements removed. It defines how to communicate (interface) with the subprogram, but not how it works internally.
The most convenient way to create Fortran interfaces is to copy and paste the subprogram into a module, "gut" it, and use that module in any subprogram that calls the subprogram. This way, we need only write out the interface for each subprogram once.
The easiest way to create a C prototype is by copying and pasting the function header and adding a semicolon.
There is also a free program called cproto that generates prototypes directly from C source code.
There are basically two ways to pass arguments to a subprogram in any language. Either we can send a copy of the value (pass by value), or, since every value is stored somewhere in memory, we can send its address (pass by reference).
When passing by value, the receiving argument variable in the subprogram is not a dummy variable, but is instead like any other local variable. It has its own memory location, which receives a copy of the argument value passed from the caller. This is how all arguments are passed in C.
Example 21.3. Sample C Code Passing by Value
x = 5; y = power(2.0, exponent); ... double power(double base, int exponent) { }
In Example 21.3, “Sample C Code Passing by Value”, the argument variable base gets a copy of the argument 2.0, and the argument variable exponent gets a copy of the value of the argument exponent in the caller.
Table 21.2. Pass by Value
Address | Name | Content |
---|---|---|
1000 | 2.0 | 2.0 |
1008 | exponent (caller's namespace) | 5 |
2000 | base | 2.0 |
2008 | exponent (power's namespace) | 5 |
Since the variable exponent in power() represents a different memory address than exponent in the caller, changes to the variable exponent within the power function have no effect on exponent in the caller. Arguments passed to functions by value are therefore protected from side effects, inadvertent changes to their value. This is usually what we want when calling a subprogram. Imagine how annoyed you would be if you called sin(angle) and the sin() function changed the value of angle!
When an argument is passed by reference, the address of the data is sent to the subprogram rather than a copy of the value. That is, each time a subprogram is called, the argument variable in the subprogram is assigned the same address as the argument sent by the caller.
All simple arguments in Fortran are passed by reference.
exponent = 5 y = power(2.0d0, exponent)
Table 21.3. Pass by Reference
Address | Name | Content |
---|---|---|
1000 | 2.0d0, base | 2.0 |
1008 | exponent (caller), exponent (power) | 5 |
As Table 21.3, “Pass by Reference” shows, when the power function is called, base assumes the address of the constant 2.0d0, and exponent assumes the address of the variable exponent in the caller.
If we called power(a, b)
,
then base would assume the address of a, and exponent
would assume the address of b.
As a result of this, changes to the variable exponent
in the power function would alter the value of exponent in the
subprogram that calls power(2.0d0, exponent)
,
and will alter the value of b in the subprogram that
calls power(a, b)
.
This is not always desirable, so Fortran provides a way to protect arguments from alteration even though they are always passed by reference. This feature is discussed in the section called “Intent”.
In C, all arguments are passed by value. If we want a function to know the address of a variable, i.e. we want to pass it by reference, we must explicitly pass the address of the variable. You have already seen this when using the scanf() library function.
scanf("%d", &x);
The scanf() function needs the address of the variable so that it can store the input value in it.
Another subprogram that needs the address of its arguments is the Fortran swap subroutine shown earlier. The implementation of an equivalent C function is shown in Chapter 23, Memory Addresses and Pointers.
Since all simple variables in Fortran are passed by reference, the arguments passed to them could be vulnerable to side-effects.
If the argument passed by reference to a subprogram is a variable, its value could be altered, which means that the caller is not independent from the subprograms it calls, and hence the program will be harder to debug.
If the argument passed is not a variable (it is a constant or an expression), then it does not make sense to alter the argument variable, since it does not represent a variable in the caller. In this case, Fortran will produce an error.
Fortunately, Fortran provides a mechanism for protecting arguments, so that we can pass variables as arguments without worrying that they could be modified, and we can pass constants and expressions without causing an error.
Fortran handles this using a modifier in the variable definition called intent. The intent of an argument variable is one of the three values in, out, or inout.
If the intent is in, then the argument variable is meant to take in information from the caller only. The value received from the caller is used by the subprogram, but cannot be modified, i.e. it is read-only. This is the type of argument used by a function like sin() or a subroutine like write.
If the intent is out, then the value received from the caller in the argument variable is not used, but the subroutine alters the value of the argument variable in order to send information back to the caller. The arguments to the intrinsic subroutine read are intent(out), since the read statement has no use for the values in the variables before read is called, but its purpose is to place new values in them. This is the type of argument used by a subroutine like read.
If the intent is inout, then the argument variable is meant to take in information from the caller and send information back. Hence, the subprogram will receive a value in the argument variable that it both uses and modifies for the caller. This situation is fairly rare. Usually, arguments are used to either receive information from the caller, or to send information back, but not both.
Note that all arguments to functions are intent(in), since the purpose of a function is to compute and return a single value through the return value. Subroutines generally use a mixture of intent(in) and intent(out) arguments.