Problem using 'C' preprocessor with gfortran

Hi all, I have the following problem:
A sample code:

program testing_ground
    implicit none
    #ifdef _WIN32
      print *,'Windows'
    #else
      print *,'Linux'
    #endif
end program testing_ground

Tested in Windows 10 with: GNU Fortran (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0
And in WSL with: GNU Fortran (Ubuntu 9.3.0-10ubuntu2~18.04) 9.3.0

compiling with either: gfortran -o test test.F90 or gfortran -cpp -o test test.f90 produces the following error:

test.f90:4:5:

    4 |     #ifdef _WIN32
      |     1
Error: Invalid character in name at (1)
test.f90:6:5:

    6 |     #else
      |     1
Error: Invalid character in name at (1)
test.f90:8:5:

    8 |     #endif
      |     1
Error: Invalid character in name at (1)

However in the next test everything works fine:

#include "macros.fi"
program testing_ground
    implicit none
    print*,'file name:', FL
    print*,'line:', LN
end program testing_ground

where macros.fi is a separate file with:

#ifndef FL
#define FL __FILE__
#endif

#ifndef LN
#define LN __LINE__
#endif
$ gfortran -o test test2.F90
$ ./test

result:

file name:test2.F90
line:           5

Why the first case that I check the operating system does not work? Am I missing something?

1 Like

@stavros
I think you can not indent the C directives:

program testing_ground
    implicit none
#ifdef _WIN32
      print *,'Windows'
#else
      print *,'Linux'
#endif
end program testing_ground

I am not 100% sure, but I do not remember having ever seen # indentation in C code (for the first level of directives).

Updated: it works without indentation

$ gfortran -cpp test2.f90
$ ./a.out
 Linux
2 Likes

The 1999 C standard says:

A preprocessing directive consists of a sequence of preprocessing tokens that satisfies the following constraints: The first token in the sequence is a # preprocessing token that (at the start of translation phase 4) is either the first character in the source file (optionally after white space containing no new-line characters) or that follows white space containing at least one new-line character. The last token in the sequence is the first new-line character that follows the first token in the sequence.143) A new-line character ends…

But I do not know what is the “translation phase 4” in the compilation process, so it does not really help…

1 Like

Thank you so much! This was the issue. I must have spent the whole afternoon yesterday looking for a solution…:frowning_face: It never occurred to me that it would be the indentation.

2 Likes

Ok it seems that I solved the compile error, but I haven’t solved my main problem, i.e. identifing the OS. For some reason non of the predefined macros are recognised in both WSL and Windows10. Just because (in the previous post) I had #else it printed Linux, with #elif it doesn’t find it.
sample code:

program testing_ground
implicit none
#ifdef __linux__
  print*,'__linux__'
#elif linux 
  print*,'linux'
#elif __unix__
  print*,'__unix__'
#elif __gnu_linux__
  print*,'__gnu_linux__'
#elif _WIN32
  print*,'_WIN32'
#elif _WIN64
  print*,'_WIN64'
#elif __CYGWIN__
  print*,'__CYGWIN__'
#elif __MINGW64__
  print*,'__MINGW64__'
#endif 
end program testing_ground

ps. I tested a sample code I found in SO in c++ compiled with gcc and it works fine, I just can’t make it work with gfortran.

I think the only robust way to identify the OS is from the build system (such as CMake, later fpm, etc.), that passes the proper defines into source files and then you can use the pre-processor to distinguish.

1 Like

Hi @stavros
After a little digging around (with MINGW) it seems that gfortran does not have the same predefined macros as gcc.

To see predefined macros for gfortran and gcc respectively:

$ touch foo.f90; gfortran -cpp -E -dM foo.f90
$ gcc -dM -E - < /dev/null

Even when I ran your snippet through gcc (gcc -E) it seems to have detected the fortran file extension and it still didn’t work.

I was able to get it working by passing it to cpp directly:

$ cpp -P testing_ground.F90
program testing_ground
implicit none
  print*,'_WIN32'
end program testing_ground

I will point out that on my system, both _WIN32 and _WIN64 are defined as well as both __MINGW32__ and __MINGW64, so I agree with @certik that this may not be so robust or portable.

2 Likes

I concur with Certik, Let CMake determine the OS. For me the process is CMake -> Ninja. (The new version of Ninja picks up native FORTRAN support … you no longer need the Kitware version)

The WIN32 & WIN64 issue is not unique, I see those in the C/C++ compilers too.

Be careful with using preprocessor directives… they are a bit like spices. A few make life very pleasant, to many and it just doesn’t work. I have on package about 25 yrs old that has too many directives, and some seem to contradict one another… So document them well and stay consistent

4 Likes

@CandL excellent comments, I fully agree. Welcome!

Just a small note, typically the Fortran preprocessors are not exactly the same as the C-equivalent. There are problems in Fortran codes that needs to be circumvented which means that they are generally sub-sets/or different implementations of the system.

I would highly recommend you to move to fypp for preprocessing fortran in a standardized way! :slight_smile:

3 Likes

I’m not sure if this directly addresses your question, but I’d recommend having a look at the accepted answer for this stack overflow question:

It explains why the C preprocessor behaves differently in gfortran than in gcc, and what you can do to work around it.

As others have said, fypp is a better preprocessor and CMake can identify the OS, but I don’t think there’s anything wrong with using the c preprocessor for some things - I certainly do it.