2
$\begingroup$

In the following code, if I compile using ifort runs ok, but if I run it with gfortran appears the error: "Program received signal SIGSEGV: Segmentation fault - invalid memory reference."

Can someone told me what I'm doing bad? Or maybe is an issue of gfortran.

PROGRAM testing
IMPLICIT NONE
REAL    ::  coordN(3,2),aux(2,2),vec(2)
INTEGER ::  cara(3,2),c
!
coordN = 0.0;  cara = 0;  vec = 0.0
!
coordN(1,1)=-1.0;   coordN(1,2) = -2.0
coordN(2,1)=-10.0;  coordN(2,2) = -20.0
coordN(3,1)=-100.0; coordN(3,2) = -200.0
!
cara(1,1) = 1;  cara(1,2) = 2
cara(2,1) = 1;  cara(2,2) = 3
cara(3,1) = 3;  cara(3,2) = 2
!
PRINT*,SIZE(coordN(cara(c,:),:),1),SIZE(coordN(cara(c,:),:),2)
aux = coordN(cara(c,:),:)
!
END program testing

Note that

    PRINT*,SIZE(coordN(cara(c,:),:),1),SIZE(coordN(cara(c,:),:),2)

says that coordN(cara(c,:),:) is in fact a 2x2 real matrix. So, I can't understand why I can't do

aux = coordN(cara(c,:),:)
$\endgroup$
2
  • 1
    $\begingroup$ I just tried your code with gfortran 6.3.0 on MacOS Catalina, and it did not crash. $\endgroup$ Oct 6 '20 at 16:00
  • 2
    $\begingroup$ When I compile with gfortran@9.3.0 with -Wall, it warns me that 'c' is used uninitialized. $\endgroup$
    – cfdlab
    Oct 7 '20 at 4:13
5
$\begingroup$

The variable c isn't initialized anywhere in the program, so the value that it will have at runtime can be anything. Here I've entered a slight modification of the program you showed above:

PROGRAM testing
IMPLICIT NONE
REAL    ::  coordN(3,2),aux(2,2),vec(2)
INTEGER ::  cara(3,2),c
!
coordN = 0.0;  cara = 0;  vec = 0.0
!
coordN(1,1)=-1.0;   coordN(1,2) = -2.0
coordN(2,1)=-10.0;  coordN(2,2) = -20.0
coordN(3,1)=-100.0; coordN(3,2) = -200.0
!
cara(1,1) = 1;  cara(1,2) = 2
cara(2,1) = 1;  cara(2,2) = 3
cara(3,1) = 3;  cara(3,2) = 2
!
PRINT *, c
PRINT *, SIZE(coordN(cara(c,:),:),1),SIZE(coordN(cara(c,:),:),2)
aux = coordN(cara(c,:),:)
!
END program testing

The critical part is that this program prints out the value of c first. When I run this program on my machine, I get different values every time. On one run, I get -2147483648; on another, I get 0. On still a third, I also get 0, but -- and we'll get to this in a sec -- the gfortran runtime library gives me this error: runtime error: load of address 0x7ffc33ea35d8 with insufficient space for an object of type 'real(kind=4)'.

The reason that all of this happens is that, if you do not initialize a variable, the value it has at runtime can be complete and utter garbage. The precise nature of this garbage is platform-dependent. The value could be whatever was left in memory by the last process that touched that region of RAM. Or, somewhere along the line all of that memory could have been initialized to zero, either by your operating system as a security measure, or by the fortran runtime library. If c happens to be zero, then one possible behavior is that the memory address immediately before the start of that array will get read. That address is still out of the array bounds, but it's still likely to fall somewhere within the program stack or heap. But as I mentioned above it could also be a completely nonsensical value that exceeds, say, Avogadro's number, much less the RAM size of your machine.

This is the kind of mistake that I make all the time. Luckily, there are tricks for this kind of thing and here are some of my favorites:

  1. Compile with -fbounds-check. You can do this in Fortran because the language guarantees that you can check the size of all arrays at runtime. This compiler flag isn't possible in C where "arrays" are just contiguous regions of memory and you, the programmer, have to track sizes yourself.
  2. Compile with all warnings: -Wall -Wextra -Wpedantic. You're likely to get a lot of false positives, but I always find weird dodgy stuff in any code I write the first time around.
  3. Compile with debug flags and no optimization (-g -O0) and use valgrind on the executable: valgrind ./test. Valgrind can often pinpoint the exact line in your code where something went wrong and give you a stack trace. It might not be what you expected!
  4. Compile with sanitizers: -fsanitize=address, or -fsanitize=undefined. Sanitizers can pinpoint memory leaks, which, if you code in C, is a real lifesaver. Can work where valgrind fails, can fail where valgrind works. That error message I got on one execution when c = 0 was from using the address sanitizer.
  5. If you're encountering a weird heisenbug that doesn't show up on every program invocation, you can use rr to deterministically record several program traces that you can then manually step through later with GDB.

I really detest programming language dogmatism but there are some factors worth considering here. Fortran provides stronger runtime guarantees than C does about arrays sizes, so tricks like -fbounds-check are possible and very useful. On the other hand, the tooling for C is substantially better than for Fortran because it's used so much more widely and because failures have security implications. For example, when I was trying to find which compiler flags would make this problem really obvious, my first thought was: aha, I'll compile with -Wall and I'll get a warning on use of an uninitialized value. But I was surprised to find that gfortran didn't throw a warning at all and the only flag that reliably helps is -fbounds-check. If I wrote an equivalent C program, gcc would have given a warning about undefined behavior. This isn't to say that you should use C instead of Fortran or vice versa -- it's rare that you get the luxury of choosing at all. But there are differences and you should be aware of them.

$\endgroup$
1
  • $\begingroup$ Actually when I compile with gfortran@9.3.0 with -Wall, it did warn that 'c' is used uninitialized. $\endgroup$
    – cfdlab
    Oct 7 '20 at 4:14

Not the answer you're looking for? Browse other questions tagged or ask your own question.