The GCC commandline option -DFOO[=value] has
the same effect as placing #define FOO [val] on an imaginary source line before the real first line of every
source file compiled by the command. The option -UFOO has the same effect as placing
#undef FOO on an imaginary line. If there are N such options on the commandline then it as if there are N imaginary lines.
However the C Standard - C17 here, but
likewise any other edition - states:
7.1.3 Reserved identifiers ... All identifiers that begin with an underscore and either an uppercase letter or another under-
score are always reserved for any use, except those identifiers which are lexically identical to
keywords ... If the program declares or defines an identifier in a context in
which it is reserved (other than as allowed by 7.1.4), or defines a reserved identifier as a macro name,
the behavior is undefined.
So it is UB for you to define, e.g. __linux__ or _WIN32, and the likeliest (and kindest) UB that
your compiler will exihibit if you try to is simply to ignore your definitions that
conflict with its reserved ones.
Such reserved macro names are pre-defined by your compiler. E.g. this compiler:
$ gcc --version | head -n1
gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
$ echo | gcc -dM -E - | grep linux; echo Done
#define __linux 1
#define __gnu_linux__ 1
#define linux 1
#define __linux__ 1
Done
$ echo | gcc -dM -E - | grep x86; echo Done
#define __x86_64 1
#define __x86_64__ 1
Done
was built to target x86_64 GNU/Linux and not built to target Windows:
$ echo | gcc -dM -E - | grep _WIN32; echo Done
Done
Whereas this cross-compiler:
$ echo | x86_64-w64-mingw32-gcc-win32 -dM -E - | grep linux ; echo Done
Done
$ echo | x86_64-w64-mingw32-gcc-win32 -dM -E - | grep _WIN32; echo Done
#define __WIN32__ 1
#define _WIN32 1
#define __WIN32 1
Done
$ echo | x86_64-w64-mingw32-gcc-win32 -dM -E - | grep x86 ; echo Done
#define __x86_64 1
#define __x86_64__ 1
Done
was built to target x86_64 Windows. And this one:
$ echo | aarch64-linux-gnu-gcc -dM -E - | grep linux ; echo Done
#define __linux 1
#define __gnu_linux__ 1
#define linux 1
#define __linux__ 1
Done
$ echo | aarch64-linux-gnu-gcc -dM -E - | grep x86 ; echo Done
Done
echo | aarch64-linux-gnu-gcc -dM -E - | grep ARM_ARCH ; echo Done
#define __ARM_ARCH_PROFILE 65
#define __ARM_ARCH 8
#define __ARM_ARCH_8A 1
#define __ARM_ARCH_ISA_A64 1
Done
was built to target ARM 64 GNU/Linux.
These pre-defined macros are not defaults that you can override. They report
immutable features of the compiler that programmers can query with the
like of:
#ifdef __linux__
...
#endif
In other words it is not the case that any GCC compiler can be made act as any desired
cross-compiler by feeding it the right macro definitions.
A few pre-defined macros are mandated by the Standard. See 6.10.8 Predefined macro names
in the same document. The rest (including
all 3.7.3 System-specific Predefined Macros like the ones mentioned) are chosen and defined by the compiler implementer, so there is no over-arching regulation
of what they are, how they are defined, or what they mean.
-D __linux__-Dand the macro name); why isn't gcc passing macro value from the command line? (code overrides command line); what's the default value when a symbol is defined on command line? (1).__linux__does not really "force compile for linux". It can cause Linux-specific code to be processed instead of Windows-specific code, but it does not cause the executable to be in a format that Linux recognizes.__linux__predefined, so you don't have to add that.