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:
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
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 .
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
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.
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 |
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
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?
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
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.
Definition vs Declaration in C and C++: What’s the Difference?
Learn the difference between declarations and definitions in C and C++ with simple examples, common mistakes, and practical usage.
Difference Between #include <...> and #include "..." in C and C++
Learn the difference between #include with angle brackets and quotes in C and C++, including search paths, examples, and common mistakes.