Fortran Allocable Array Assignment C++

Arrays

An array is a series of elements of the same type placed in contiguous memory locations that can be individually referenced by adding an index to a unique identifier.

That means that, for example, five values of type can be declared as an array without having to declare 5 different variables (each with its own identifier). Instead, using an array, the five values are stored in contiguous memory locations, and all five can be accessed using the same identifier, with the proper index.

For example, an array containing 5 integer values of type called could be represented as:


where each blank panel represents an element of the array. In this case, these are values of type . These elements are numbered from 0 to 4, being 0 the first and 4 the last; In C++, the first element in an array is always numbered with a zero (not a one), no matter its length.

Like a regular variable, an array must be declared before it is used. A typical declaration for an array in C++ is:


where is a valid type (such as , ...), is a valid identifier and the field (which is always enclosed in square brackets ), specifies the length of the array in terms of the number of elements.

Therefore, the array, with five elements of type , can be declared as:



NOTE: The field within square brackets , representing the number of elements in the array, must be a constant expression, since arrays are blocks of static memory whose size must be determined at compile time, before the program runs.

Initializing arrays

By default, regular arrays of local scope (for example, those declared within a function) are left uninitialized. This means that none of its elements are set to any particular value; their contents are undetermined at the point the array is declared.

But the elements in an array can be explicitly initialized to specific values when it is declared, by enclosing those initial values in braces {}. For example:



This statement declares an array that can be represented like this:


The number of values between braces shall not be greater than the number of elements in the array. For example, in the example above, was declared having 5 elements (as specified by the number enclosed in square brackets, ), and the braces contained exactly 5 values, one for each element. If declared with less, the remaining elements are set to their default values (which for fundamental types, means they are filled with zeroes). For example:



Will create an array like this:


The initializer can even have no values, just the braces:



This creates an array of five values, each initialized with a value of zero:


When an initialization of values is provided for an array, C++ allows the possibility of leaving the square brackets empty . In this case, the compiler will assume automatically a size for the array that matches the number of values included between the braces :



After this declaration, array would be 5 long, since we have provided 5 initialization values.

Finally, the evolution of C++ has led to the adoption of universal initialization also for arrays. Therefore, there is no longer need for the equal sign between the declaration and the initializer. Both these statements are equivalent:



Static arrays, and those declared directly in a namespace (outside any function), are always initialized. If no explicit initializer is specified, all the elements are default-initialized (with zeroes, for fundamental types).

Accessing the values of an array

The values of any of the elements in an array can be accessed just like the value of a regular variable of the same type. The syntax is:


Following the previous examples in which had 5 elements and each of those elements was of type , the name which can be used to refer to each element is the following:


For example, the following statement stores the value 75 in the third element of :



and, for example, the following copies the value of the third element of to a variable called :



Therefore, the expression is itself a variable of type .

Notice that the third element of is specified , since the first one is , the second one is , and therefore, the third one is . By this same reason, its last element is . Therefore, if we write , we would be accessing the sixth element of , and therefore actually exceeding the size of the array.

In C++, it is syntactically correct to exceed the valid range of indices for an array. This can create problems, since accessing out-of-range elements do not cause errors on compilation, but can cause errors on runtime. The reason for this being allowed will be seen in a later chapter when pointers are introduced.

At this point, it is important to be able to clearly distinguish between the two uses that brackets have related to arrays. They perform two different tasks: one is to specify the size of arrays when they are declared; and the second one is to specify indices for concrete array elements when they are accessed. Do not confuse these two possible uses of brackets with arrays.



The main difference is that the declaration is preceded by the type of the elements, while the access is not.

Some other valid operations with arrays:



For example:



Multidimensional arrays

Multidimensional arrays can be described as "arrays of arrays". For example, a bidimensional array can be imagined as a two-dimensional table made of elements, all of them of a same uniform data type.


represents a bidimensional array of 3 per 5 elements of type . The C++ syntax for this is:



and, for example, the way to reference the second element vertically and fourth horizontally in an expression would be:




(remember that array indices always begin with zero).

Multidimensional arrays are not limited to two indices (i.e., two dimensions). They can contain as many indices as needed. Although be careful: the amount of memory needed for an array increases exponentially with each dimension. For example:



declares an array with an element of type for each second in a century. This amounts to more than 3 billion ! So this declaration would consume more than 3 gigabytes of memory!

At the end, multidimensional arrays are just an abstraction for programmers, since the same results can be achieved with a simple array, by multiplying its indices:



With the only difference that with multidimensional arrays, the compiler automatically remembers the depth of each imaginary dimension. The following two pieces of code produce the exact same result, but one uses a bidimensional array while the other uses a simple array:

multidimensional arraypseudo-multidimensional array

None of the two code snippets above produce any output on the screen, but both assign values to the memory block called jimmy in the following way:


Note that the code uses defined constants for the width and height, instead of using directly their numerical values. This gives the code a better readability, and allows changes in the code to be made easily in one place.

Arrays as parameters

At some point, we may need to pass an array to a function as a parameter. In C++, it is not possible to pass the entire block of memory represented by an array to a function directly as an argument. But what can be passed instead is its address. In practice, this has almost the same effect, and it is a much faster and more efficient operation.

To accept an array as parameter for a function, the parameters can be declared as the array type, but with empty brackets, omitting the actual size of the array. For example:



This function accepts a parameter of type "array of " called . In order to pass to this function an array declared as:



it would be enough to write a call like this:



Here you have a complete example:



In the code above, the first parameter () accepts any array whose elements are of type , whatever its length. For that reason, we have included a second parameter that tells the function the length of each array that we pass to it as its first parameter. This allows the for loop that prints out the array to know the range to iterate in the array passed, without going out of range.

In a function declaration, it is also possible to include multidimensional arrays. The format for a tridimensional array parameter is:



For example, a function with a multidimensional array as argument could be:



Notice that the first brackets are left empty, while the following ones specify sizes for their respective dimensions. This is necessary in order for the compiler to be able to determine the depth of each additional dimension.

In a way, passing an array as argument always loses a dimension. The reason behind is that, for historical reasons, arrays cannot be directly copied, and thus what is really passed is a pointer. This is a common source of errors for novice programmers. Although a clear understanding of pointers, explained in a coming chapter, helps a lot.

Library arrays

The arrays explained above are directly implemented as a language feature, inherited from the C language. They are a great feature, but by restricting its copy and easily decay into pointers, they probably suffer from an excess of optimization.

To overcome some of these issues with language built-in arrays, C++ provides an alternative array type as a standard container. It is a type template (a class template, in fact) defined in header .

Containers are a library feature that falls out of the scope of this tutorial, and thus the class will not be explained in detail here. Suffice it to say that they operate in a similar way to built-in arrays, except that they allow being copied (an actually expensive operation that copies the entire block of memory, and thus to use with care) and decay into pointers only when explicitly told to do so (by means of its member ).

Just as an example, these are two versions of the same example using the language built-in array described in this chapter, and the container in the library:

language built-in arraycontainer library array

As you can see, both kinds of arrays use the same syntax to access its elements: . Other than that, the main differences lay on the declaration of the array, and the inclusion of an additional header for the library array. Notice also how it is easy to access the size of the library array.





Over the years we have made lots of interesting and fun mistakes in Fortran 90 that we would like to share with you. We welcome your contributions and experiences so that we can share your pain.

Topics

These "gotchas" are nasty because they will not fail on some machines, while failing on others (given various combinations of compilers and machine platforms). In this example an optional argument is used to determine if a header is printed.

Explanation
The first method is not safe because the compiler is allowed to evaluate the header argument before the present function is evaluated. If the header argument is not in fact present an out of bounds memory reference could occur, which could cause a failure.

In this example we assign components of a derived type with intent(out).

Explanation
The problem is that when intent(out) is used with a derived type, any component not assigned in a procedure could become undefined on exit. For example, even though a%y was defined on entry to this routine, it could become undefined on exit because it was never assigned within the routine. The lesson is that all components of a derived type should be assigned within a procedure, when intent(out) is used. Intent(out) behaves like the result variable in a function: all components must be assigned.

As an alternative, use intent(inout).

Many people think that the new non-advancing I/O in Fortran 90 is the same as stream I/O in other languages. It is not.

We expect this program to print 128 X's in a row. However, unexpected behavior may occur if the record length for unit 6 is less than 128.

One can inquire the record length in the follow way:

Explanation
All Fortran I/O is still record based. Non-advancing I/O allows partial reads and writes within a record. For many compilers the default record length is very large (e.g., 2147483647) giving the appearance of stream I/O. This is not true for all compilers however.

On some compilers it is possible to set the record length as follows:

On other compilers unit 6 is preconnected and the record length cannot be changed. (Thanks to Jon Richards of the USGS for this tip.)

Note that unit 6 and unit * are not necessarily the same. Although they both may point to the default output device, with non-advancing I/O, each could keep track of the current location in its own record separately. Therefore we advise choosing one default unit and sticking with it.

One must be careful when initializing a locally declared variable.

Explanation
A local variable that is initialized when declared has an implicit save attribute. ke is initialized only the first time the function is called. On subsequent calls the old value of ke is retained. This is a real suprise to C programmers.

To avoid confusion it is best to add the save attribute to such locally initialized variables explicitly, even though this is redundant.

Explanation
The subroutine incb uses a Fortran 90 style assumed shape array (containing dimension(:)). Such routines must either be in a module, or have an explicit interface wherever they are used. In this example, neither one was true.

One correct way to call such procedures is to use an explicit interface as follows:

If the routine is in a module interfaces are generated automatically and do not need to be explicitly written. If interfaces are used, the interface MUST match the actual function.

Explanation
The interface declaration must always match the actual subroutine declaration. In this case, the interface statement refers to a Fortran 90 style assumed shape array. The actual subroutine refers to a Fortran 77 explicit shape array. The lesson here is: Interfaces to Fortran 77 style routines must only use Fortran 77 style constructs.

In this example, it is permitted to leave out the interface altogether since routines without interfaces are treated as Fortran77 style routines by default. However, if the interface is left out, the compiler will no longer check whether the arguments of calling procedures agree with the arguments listed in the interface.

Fortran 90 allows the same function name to be used for different actual functions, so long as the arguments to the functions differ. One would expect that the functions first_sub and second_sub below would be different, because in first_sub, the first argument is a real and the second is an integer, while in second_sub the arguments are reversed.

So that one could define a generic function first_or_second below:

This is NOT so.

Explanation
The reason is that Fortran 90 allows procedures to be called by name (keyword) arguments. The following

does not work because when called by keyword, first_sub and second_sub are indistinguishable,

and therefore a generic function cannot be defined. A generic function must be able to distinguish its arguments by type AND by name.

The solution is to not use the same dummy argument name in both procedures. For example, the following would work:

Fortran 90 has 3 ways to implement dynamic memory: Automatic arrays, allocatable arrays, and pointers.

Automatic arrays are automatically created on entry and deleted on exit from a procedure, and they are safest and easiest to use. Allocatable arrays require the user to manually create and delete them, and should only be used if automatic creation and deletion is not the desired behavior.

Pointers are the most error prone and should only be used when allocatable arrays are not possible, e.g., when one desires an array to be a component of a derived type.

Many people think that the status of a pointer which has never been associated is .not. associated. This is false.

In this example we are allocating a local_table on first entry that is to be reused on subsequent entries.

Explanation
When a pointer is declared its status is undefined, and cannot be safely queried with the associated intrinsic. A second variable is introduced to nullify the pointer on first entry so that its status can be safely tested. This is not a problem in Fortran 95 which allows one to nullify a pointer on declaration.

Note that the save attribute for local_table is necessary to guarantee that the array and the pointer status are preserved on subsequent entries. We recommend that the save attribute should always be used when pointers and allocatable arrays are allocated in procedures.

One must be careful with overloading the assignment operator.

In this module we have created a private type which contains a pointer and a public procedure to assign that pointer.

In this main program we intend to assign the pointer component x%pr to the variable a, x%pr =>a. We cannot do so directly because the components of mytype are private. One must use a public procedure to do so. Furthermore, to simplify the syntax one might be tempted to use an overloaded assignment operator (=).

Don't give into this temptation! The only safe way to accomplish this is to call the procedure directly.

Explanation
The Fortran 90 standard says that the right hand side of an assignment operator is an expression that may potentially only persist for the duration of the call. In other words, x%pr could inadvertently point to a temporary copy of the variable a.

Thanks to Henry Zongaro of IBM for pointing this out. (We never would have figured this one out on our own.)

Also, James Giles found a subtle point regarding this example. We did not include "target" in the declaration of the real variable "a" (this has been corrected above). In James' words:

"Notice that for this to really work, the actual argument, 'a', must be declared with the target attribute. You correctly declare the dummy argument in the assign_pointer routine with the target attribute, but the actual argument must also have that attribute (otherwise it's illegal for any pointer to be associated with it). Just a minor point..."

When creating a hierarchy of pointers to pointers, each level of pointers must be allocated before being used.

Explanation
This example creates a pointer to a pointer to an array of reals where the first pointer has not been allocated. For safety one should always either allocate or nullify the parent pointer immediately after its declaration. The child pointer cannot be allocated before the parent. Since the child pointer may be allocated elsewhere in the code, it is convenient to use constructor routines for this purpose.

Each child constructor can safely allocate or nullify its pointers only when it can be sure that its parent's pointers have been allocated or nullified.

Categories: 1

0 Replies to “Fortran Allocable Array Assignment C++”

Leave a comment

L'indirizzo email non verrà pubblicato. I campi obbligatori sono contrassegnati *