"Nezhdanchiki" Fortran language


    Many of us, while studying programming at universities or at home, did this in C / C ++. Of course, it all depends on the time at which our acquaintance with programming languages ​​began. Say someone started with Fortran, others started with Basic or Delphi, but it’s worth recognizing that the proportion of programmers who started their thorny path with C / C ++ is the largest. Why am I doing all this? When we are faced with the task of learning a new language and writing code on it, we often rely on how I would write it in my “base” language. We narrow the question - if you need to write something in Fortran, then we recall how it would be implemented in C and do it by analogy. Once again faced with the subtlety of the language, which led to an absolutely inoperative algorithm and a big problem escalated to me, I decided to find as many nuances of the Fortran language as possible (Fortran 90/95), compared to C, which I personally encountered. This is a kind of "unexpected people" that you clearly did not plan to see, but they bang - and surfaced!
    Of course, we will not talk about syntax - in each language it is different. I will try to talk about global things that can change everything “upside down”. Go!

    Passing arguments to functions
    We all remember that with such code in C you cannot change the value of the variable a in the calling main function:
    void modify_a(int a)
    {
      a = 6;
    }
    int main()
    {
      int a = 5;
      modify_a(a);
      return 0;
    }
    

    That's right - the arguments to the function in C are passed by value, so changing a in the modify_a function will fail. To do this, you need to pass the argument by reference and then we will work with the same a passed from the called function.
    So, the "unexpected" number of "times" is that in Fortran the opposite is true! Arguments are passed to the function by reference, and similar code will completely change the value of a :
    a = 5
    call modify_a (a)
    contains
    subroutine modify_a (a)
      integer a
      a = 6
     end subroutine modify_a
     end
    

    I think that everyone understands the problems that may arise from ignorance of this fact. Moreover, this specificity can manifest itself in many places, in particular, when working with pointers, but there will be a separate discussion about this.

    Working with arrays
    By default, indexing arrays in Fortran starts at 1 , not 0 , as in C. That is, real a (10) gives us an array from 1 to 10 , and in C float a [10] goes from 0 to 9 . However, we can define an array as real a (0: 100) in Fortran.

    In addition, multidimensional arrays are stored in Fortran memory in columns. Thus the ordinary matrix

    is located in memory like this:

    Do not forget about this when working with arrays, especially if we pass them to / from a function in C through libraries. Fortran

    undeclared variables
    by default will not swear at data that we have not declared explicitly, because here there is the concept of implicit data types. It went from ancient times, and the idea is that we can immediately work with data, and their type will be determined depending on the first letter in the name - how tricky!
    Attempting to compile code with the C compiler will predictably produce the error 'b: undeclared identifier' :
    int main()
    {
    	b = 5;
    }
    

    In Fortran, it will work with a bang:
    i = 5
    end
    

    How many absolutely diverse errors in the code can be from this. Therefore, do not forget to add IMPLICIT NONE to the code , which prohibits such "games" with implicit declarations:
    implicit none
    i = 5
    end
    

    And immediately we see the error: error # 6404: This name does not have a type, and must have an explicit type. [I]
    By the way, the Fortran language is not case-sensitive, so the variables a and A are one and the same. But this is the syntax that I promised not to talk about.

    Initialization of local variables
    It would seem that such an initialization might be bad:
    real :: a = 0.0
    

    And how does it differ from this:
    real a
    a = 0.0
    

    An unexpected surprise for developers in C - in Fortran, there is a fundamental difference in this! If a local variable is initialized at the time of declaration, then the SAVE attribute is implicitly applied to it . What is this attribute? If a variable is declared as SAVE (explicitly or implicitly), then it is static, which means it is initialized only the first time it enters a function. Subsequent entries to the function retain the previous value. And this may not be what we expect at all. As a tip, avoid such initializations, and use the SAVE attribute explicitly if necessary . By the way, the compiler even has a separate option -save, which allows you to change the default settings (highlighting on the stack) and make all variables static (except for cases of recursive functions and those variables that are explicitly declared as AUTOMATIC ).

    Pointers
    Yes, Fortran also has the concept of pointers. But they are used much less often, because you can dynamically allocate memory in it without their help, and the arguments are passed by reference. It is worth noting that the pointer mechanism itself works differently in Fortran, so I will dwell on this in more detail.
    Here you cannot make a pointer to any object - only to one that is declared in a special way. For example, like this:
    real, target :: a
    real, pointer :: pa
    pa => a
    

    Using the operator => we associate the pointer pa with the object a . Do not try to perform an assignment operation instead of =>. Everything will be successfully assembled, but will fall in runtime. So those who are used to simply assigning pointers to C will have to be forced to write => instead of = each time . First you forget, but then you get involved.
    If we want the pointer not to be associated with an object, we use nullify (pa) - this is a kind of initialization of the pointer. When we simply declare a pointer, its status in Fortran is undefined, and a function that checks its association with objects ( associated (pa) ) will not work correctly.
    By the way, why can't you associate a pointer with any variable of the same type, as in C? First of all, I wanted it in the standardization committee. Just kidding. Most likely, the whole point is in yet another level of protection against potential errors - just like that, now we just won’t be able to associate the pointer with a random variable, well, such a restriction gives the compiler more information, and, therefore, more options for optimizing the code.
    In addition to the fact that the type of the pointer and the object must match, and the object itself must be declared with the TARGET attribute , there is also a restriction on the dimension of arrays. Say, if we work with one-dimensional arrays, then the pointer must be declared accordingly:
    real, target :: b(1000)
    real, pointer :: pb(:)
    

    If the array were two-dimensional, then the pointer would be pb (:, :) . Naturally, the size of the array in the pointer is not specified - we don’t know which array the pointer will be associated with. I think the logic is clear. After association, we can work with the pointer as usual:
    b(i) = pa*b(i+1)
    

    What is the same as writing b (i) = a * b (i + 1) . You can also assign a value, for example, pa = 1.2345 .
    Thus, the value of a will be 1.2345 . An interesting feature of Fortran pointers is that they can be used to work with part of the array.
    If we wrote b => pb , then we can work with 1000 elements of the array b through the pointer pb .
    But you can write like this:
    pb => b(201:300)
    

    In this case, we will work with an array of only 100 elements, and pb (1) is b (201) .
    It's funny how you can use the allocate memory function in the case of pointers. By writing allocate (pb (20)) we will allocate an additional 20 elements of an array of type real , which will be available only through the pb pointer .
    In general, to a person accustomed to C, all this will seem unusual. But, if you start writing code, then you get used to it quickly enough, and everything starts to seem convenient.
    The developer who came up with the idea of ​​writing this blog also thought so and worked actively with the left and right pointers, creating code whose algorithm uses a tree, but did not take into account one feature. This Fortune Code corresponded on Fortran:
    void rotate_left(rbtree t, node n) 
    {
      node r = n->right;
    ...
    

    The node structure has fields containing node * pointers , for example right .
    A local variable r is created in the function , it is assigned the value n-> right, and so on and so forth. The implementation on Fortran turned out like this:
    subroutine rotate_left(t, n)
    type(rbtree_t) :: t
    type(rbtree_node_t), pointer :: n
    type(rbtree_node_t), pointer :: r
    r => n%right
    ...
    

    And here, at the very beginning, lies the "error of errors." We associated the r pointer with n% right . Changing r in the subsequent code , we will change n% right , in contrast to C, where only the local variable r will change . As a result, the whole tree turned into something incomprehensible. The way out is another local pointer:
    subroutine rotate_left(t, n_arg)
    type(rbtree_t) :: t
    type(rbtree_node_t), pointer :: n_arg
    type(rbtree_node_t), pointer :: r
    type(rbtree_node_t), pointer :: n
    n => n_arg
    r => n%right
    ...
    

    In this case, if we subsequently change the association of the pointer n , then this will not affect the “external” n_arg in any way .

    Thongs
    And finally, one little feature to spoil a great amount of storage in mixed applications (C and Fortran). What do you think may be the difference when working with strings in C:
    char string[80]="test";
    

    And Fortran:
    character(len=80) :: string
    string = "test"
    

    The debugger will help easily. In this case, in Fortran, the remaining unused bytes are clogged with spaces. At the same time, there is no line ending character / C typical of C , so you need to be extremely careful when transferring thongs from Fortran to C and vice versa. Again, I will say that in order to work safely with C and Fortran, you need to use the special module ISO_C_BINDING , which resolves this difference and many other problems.

    This concludes my story. Now you know exactly the most important differences between C and Fortran, and if you have to write code on the latter, I think you will do it no worse than on C, right? Well, this post will help.

    Also popular now: