Question
In C, why can GCC's preprocessor replace the lowercase word linux with the constant 1, even when I did not write a #define linux myself?
For example:
#include <stdio.h>
int main(void)
{
int linux = 5;
return 0;
}
If I run only the preprocessing step:
gcc -E test.c
I can get output like this:
int main(void)
{
int 1 = 5;
return 0;
}
That obviously causes a compilation error. There is also no visible #define linux inside stdio.h, so where does this replacement come from, and why does GCC do it?
Short Answer
By the end of this page, you will understand what predefined macros are in C, why compilers like GCC provide platform-specific macros such as linux, how the preprocessor replaces identifiers before compilation, and how to write safer, more portable code by using standard feature checks like __linux__ instead of relying on old non-standard macros.
Concept
In C, the preprocessor runs before the actual compiler. Its job is to handle directives such as:
#include#define#if#ifdef
A macro is a text substitution rule. If the preprocessor sees a macro name, it replaces it with its defined value before the compiler parses the code.
For example:
#define SIZE 10
int arr[SIZE];
After preprocessing, that becomes:
int arr[10];
Why linux becomes 1
Some compilers and toolchains define predefined macros automatically. These are not necessarily written in your source file or in headers like stdio.h. Instead, the compiler injects them internally.
Historically, GCC defined system-specific macros such as:
linux
unix
sun
and gave them the value 1. So the preprocessor treats this:
int linux = 5;
as if linux were a macro name, and replaces it:
int 1 = 5;
That breaks because 1 is not a valid variable name.
Why compilers define macros like this
Compilers define platform macros so source code can detect the target operating system or architecture:
#ifdef __linux__
printf("Running on Linux\n");
#endif
This is useful for:
- platform-specific code
- conditional compilation
- portability across systems
- enabling OS-specific APIs
Why this is surprising
The confusing part is that linux looks like an ordinary identifier. Beginners expect only names introduced with #define in their own code or included headers to be replaced. But predefined macros come from the compiler itself.
Modern convention
Modern portable code should use reserved, compiler-provided names such as:
__linux____unix____APPLE___WIN32
These names are intentionally shaped to avoid collisions with normal variable names. Older bare names like linux are considered non-standard and problematic for exactly the reason shown in the question.
Mental Model
Think of the preprocessor as a very simple search-and-replace robot that scans your source code before the compiler reads it.
If the robot has a rule saying:
- replace
linuxwith1
then this code:
int linux = 5;
is blindly transformed into:
int 1 = 5;
The robot does not understand that linux was meant to be a variable name. It only sees a token that matches a macro name.
A good mental model is:
- Preprocessor = text/token substitution stage
- Compiler = actual C language parser
So the preprocessor can accidentally change code into something invalid long before the compiler checks whether the C syntax makes sense.
Syntax and Examples
Checking predefined macros
You can inspect GCC's predefined macros with:
gcc -dM -E - < /dev/null
This prints all macros GCC defines before compiling your code.
You may see entries like:
#define __linux__ 1
On some systems or compiler configurations, you may also see older compatibility macros.
Typical safe usage
Use standard predefined macros in conditional compilation:
#include <stdio.h>
int main(void)
{
#ifdef __linux__
printf("This is Linux.\n");
#endif
return 0;
}
Why this is safe
__linux__is meant for platform detection- names with double underscores are reserved for the implementation
- it avoids collisions with your own variable names
Example of the problem
int linux = 5;
If linux is predefined as a macro:
int 1 = 5;
That is invalid C.
Safer variable naming
int linux_count = 5;
int running_on_linux = 1;
These names are not equal to the macro token linux, so they are not replaced.
Undefining a problematic macro
If you absolutely must use a name that clashes with a macro, you can undefine it:
#undef linux
int linux = 5;
But this is usually not the best choice in real code, because:
- it can reduce portability
- it may interfere with platform checks later in the file
- it relies on non-standard behavior being present in the first place
Step by Step Execution
Consider this code:
int linux = 5;
Assume the compiler has an internal predefined macro:
#define linux 1
Now trace what happens.
Step 1: Source code is read
The preprocessor receives:
int linux = 5;
Step 2: Tokenization
It breaks the line into tokens roughly like this:
intlinux=5;
Step 3: Macro lookup
The preprocessor checks whether any token is a macro name.
int→ not a macro herelinux→ yes, macro found
Step 4: Replacement
linux is replaced with 1.
The line becomes:
int 1 = 5;
Step 5: Compiler parses the result
Now the actual C compiler sees:
int 1 = 5;
This is invalid because variable names must be identifiers, not numeric literals.
Another trace with #ifdef
#ifdef __linux__
printf("Linux\n");
#endif
Step by step:
- The preprocessor checks whether
__linux__is defined. - If yes, it keeps the
printfline. - If no, it removes that block.
- The compiler only sees the remaining code.
This is the intended use of predefined macros.
Real World Use Cases
Predefined macros are widely used in real programs when code must behave differently on different systems.
1. Platform-specific includes
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
Used when system headers differ by OS.
2. Choosing OS-specific code paths
#ifdef __linux__
printf("Using epoll support\n");
#endif
Useful for Linux-only APIs.
3. Cross-platform libraries
A library may compile on:
- Linux
- macOS
- Windows
- BSD
It can use predefined macros to include the right implementation for each platform.
4. Architecture detection
Compilers also define macros for CPU types, such as x86 or ARM, allowing optimized code paths.
5. Feature guards in build systems
Generated configuration headers and compiler macros often work together to enable or disable optional functionality.
Real Codebase Usage
In real projects, developers rarely rely on old bare macros like linux. Instead, they use safer and clearer patterns.
Common pattern: implementation-reserved macro names
#ifdef __linux__
/* Linux-specific code */
#endif
This avoids collisions with application variables.
Common pattern: guard clauses for portability
#ifndef __linux__
#error "This module requires Linux"
#endif
This fails early and clearly if the code is compiled on an unsupported platform.
Common pattern: wrapping platform differences
void sleep_one_second(void)
{
#ifdef _WIN32
Sleep(1000);
#else
sleep(1);
#endif
}
This keeps the rest of the codebase platform-neutral.
Common pattern: configuration headers
Large codebases often prefer generated config checks over direct OS-name checks:
#ifdef HAVE_EPOLL
/* use epoll */
#endif
That is often better than checking __linux__ directly because it asks:
- "Is the feature available?"
instead of:
- "What OS name are we compiling for?"
Common pattern: avoiding macro name collisions
Developers avoid naming variables after:
- common macros
- standard library names
- compiler-reserved identifiers
This reduces surprising preprocessing bugs.
Common Mistakes
1. Assuming only headers define macros
Many beginners think macros only come from lines like:
#define NAME value
inside source files or headers. But the compiler can define macros automatically.
Avoid it
Use:
gcc -dM -E - < /dev/null
when you want to inspect predefined macros.
2. Using platform names as ordinary variable names
Broken example:
int linux = 5;
int unix = 3;
These may collide with predefined macros on some systems.
Better
int linux_version = 5;
int unix_mode = 3;
3. Using old non-standard macros in portable code
Broken style:
#ifdef linux
/* ... */
#endif
This may work on some systems, but it is not the best portable choice.
Better
#ifdef __linux__
/* ... */
#endif
4. Undefining macros without understanding the effect
Problematic example:
#undef __linux__
This can break platform-specific conditions later in the file.
Avoid it
Only use #undef when you truly need it and understand all consequences.
5. Confusing preprocessing with compilation
A beginner may think the compiler "misread" the variable name. In reality:
- preprocessor changed the code first
- compiler only saw the changed result
How to avoid confusion
Use gcc -E file.c to inspect the preprocessed output when debugging macro issues.
Comparisons
| Concept | What it does | Typical example | Best use |
|---|---|---|---|
| Object-like macro | Replaces a name with a value | #define SIZE 10 | Constants for preprocessing |
| Function-like macro | Replaces a call-like pattern | #define MAX(a,b) ... | Small macro-based utilities |
| Predefined macro | Supplied by compiler | __linux__, _WIN32 | Platform or compiler detection |
| Variable | Stores a runtime value | int count = 10; | Data used while the program runs |
const variable | Read-only runtime object | const int size = 10; | Safer typed constants |
linux vs __linux__
| Name | Status | Safe to use for platform detection? | Risk of collision |
|---|---|---|---|
linux | Historical/non-standard compatibility macro on some systems | No | High |
__linux__ | Modern compiler-provided macro | Yes | Low |
Macro vs variable
| Macro | Variable |
|---|---|
| Replaced before compilation | Exists during program execution |
| No type | Has a type |
| Can accidentally rewrite code | Cannot rewrite source code |
| Used for conditional compilation | Used for storing values |
Cheat Sheet
Quick reference
Inspect predefined macros
gcc -dM -E - < /dev/null
Preprocess only
gcc -E file.c
Safe platform checks
#ifdef __linux__
#endif
#ifdef _WIN32
#endif
#ifdef __APPLE__
#endif
Avoid
#ifdef linux
#endif
and avoid variable names like:
int linux;
if your toolchain may define that macro.
Remove a macro if necessary
#undef linux
Use carefully.
Key rules
- The preprocessor runs before compilation.
- Compilers can define macros without headers.
- Macros replace matching tokens.
- Old bare platform macros can collide with variable names.
- Prefer reserved implementation macros like
__linux__. - Prefer feature detection over OS detection when possible.
FAQ
Why is linux defined even though I never wrote #define linux?
Because some macros are predefined by the compiler itself, not by your source files or headers.
Is linux a standard C macro?
No. It is a historical, non-standard platform macro that some GCC environments provided for compatibility.
What should I use instead of linux in #ifdef checks?
Use __linux__ for Linux platform detection.
Why does the preprocessor replace variable names at all?
Because macros are token-based substitutions. If a token matches a macro name, it is replaced before the compiler checks C syntax.
How can I see all macros GCC defines?
Run:
gcc -dM -E - < /dev/null
Can I still use linux as a variable name?
Only if that macro is not defined, or if you #undef linux first. In practice, it is better to choose a different variable name.
Does this happen only with Linux?
No. Similar issues can happen with any predefined or user-defined macro that has a common identifier name.
Is this a compiler bug?
No. It is expected preprocessor behavior, although old bare macros are considered poor design by modern standards.
Mini Project
Description
Build a small C program that reports which platform-specific compilation branch was selected. This demonstrates how predefined macros are used correctly for conditional compilation and helps you avoid collisions with ordinary variable names.
Goal
Create a portable C program that prints a message for Linux, Windows, macOS, or an unknown platform using compiler-provided predefined macros.
Requirements
- Write a
mainfunction that prints one platform message. - Use conditional compilation with predefined macros such as
__linux__,_WIN32, and__APPLE__. - Do not use
linuxas a variable name. - Include a fallback branch for unknown platforms.
Keep learning
Related questions
2D Array Loop Order and Cache Performance in C
Learn why swapping nested loops changes 2D array performance in C, using row-major memory layout, cache locality, and practical benchmarks.
Array-to-Pointer Conversion in C and C++ Explained
Learn what array-to-pointer conversion means in C and C++, how array decay works, and how it differs from a pointer to an array.
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.