gcc -D NAME=value: define a preprocessor macro from the command line
- tool
- C
- command
- gcc
- flag
- -D name[=definition], -U name
- default
- only the compiler's predefined macros (__STDC_VERSION__, __GNUC__, platform macros...)
gcc -DDEBUG -DLEVEL=2 main.c defines the macros DEBUG (value 1) and LEVEL (value 2) before the first line of the source is read, exactly as if the file began with #define DEBUG 1 and #define LEVEL 2. It is how builds switch features on and off, inject a version string or select a configuration without editing code. -U NAME removes a definition. Clang and GCC accept the same syntax.
Defining macros
The example uses Clang 20 from the Zig 0.15.1 toolchain (installed from PyPI with uv); cc below is that compiler, and GCC accepts the same options:
cc() { uv run -q --no-project --python 3.13 --with ziglang==0.15.1 python -m ziglang cc -fno-color-diagnostics "$@"; }
cat > app.c <<'EOF'
#include <stdio.h>
#ifndef VERSION
#define VERSION "dev"
#endif
int main(void) {
#ifdef DEBUG
puts("debug build");
#endif
printf("version %s, level %d\n", VERSION, LEVEL);
return 0;
}
EOF
echo "== no -D"
cc app.c -o app0; echo "exit: $?"
echo "== -DLEVEL=2"
cc -DLEVEL=2 app.c -o app1 && ./app1
echo "== -DLEVEL=2 -DDEBUG -DVERSION=\"1.4.0\""
cc -DLEVEL=2 -DDEBUG '-DVERSION="1.4.0"' app.c -o app2 && ./app2
echo "== -DLEVEL (no value) seen by the preprocessor"
cc -DLEVEL -E app.c | grep 'printf("version'
echo "== -DDEBUG -UDEBUG"
cc -DLEVEL=3 -DDEBUG -UDEBUG app.c -o app3 && ./app3Output
== no -D
app.c:9:47: error: use of undeclared identifier 'LEVEL'
9 | printf("version %s, level %d\n", VERSION, LEVEL);
| ^
1 error generated.
exit: 1
== -DLEVEL=2
version dev, level 2
== -DLEVEL=2 -DDEBUG -DVERSION="1.4.0"
debug build
version 1.4.0, level 2
== -DLEVEL (no value) seen by the preprocessor
printf("version %s, level %d\n", "dev", 1);
== -DDEBUG -UDEBUG
version dev, level 3Without LEVEL the code does not compile: the preprocessor leaves the name in place and the compiler reports it as an undeclared identifier. -DLEVEL alone defines it as 1, which -E (preprocess only) makes visible. The string needs its C quotes inside the shell quotes. -U after -D removes the macro again: options are processed from left to right.
Forms of the option
| Option | Equivalent | Notes |
|---|---|---|
| -DNAME | #define NAME 1 | Space after -D is also accepted: -D NAME |
| -DNAME=value | #define NAME value | Value is taken literally as tokens |
| -DNAME= | #define NAME | Defined but empty: #ifdef is true, #if NAME is an error |
| '-DNAME="text"' | #define NAME "text" | C string; keep the double quotes from the shell |
| '-DMAX(a,b)=((a)>(b)?(a):(b))' | function-like macro | Quote it for the shell |
| -UNAME | #undef NAME | Also removes predefined macros |
| -DNDEBUG | #define NDEBUG 1 | Disables assert() from assert.h |
Typical uses
gcc -O2 -DNDEBUG -o app main.c # release: assert() compiled out
gcc -D_GNU_SOURCE -o app main.c # expose GNU extensions in glibc headers
gcc -D_FILE_OFFSET_BITS=64 -o app main.c # 64-bit off_t on 32-bit Linux
gcc "-DGIT_HASH=\"$(git rev-parse --short HEAD)\"" -o app main.c
gcc -dM -E - < /dev/null | sort | head # list predefined macrosPitfalls
- A macro defined on the command line changes the meaning of every file compiled with it. Feature-test macros such as
_GNU_SOURCEmust be the same for all translation units, otherwise structures from system headers can differ between files. - Redefining a macro that the source also
#defines gives amacro redefinedwarning; guard defaults with#ifndef, asVERSIONdoes above. - Build systems may cache objects: after changing
-Dflags, rebuild everything (most generators track flags, a hand-written Makefile often does not). - Use
-Dfor configuration, not for secrets: the values end up in the binary and in build logs.
Related: gcc -I for header directories, gcc -O2 for optimization levels, and gcc -Wall -Wextra for warnings.
FAQ
What value does -DNAME get without =value?
1. gcc -DDEBUG is the same as #define DEBUG 1, so both #ifdef DEBUG and #if DEBUG are true.
How do I pass a string with -D?
Include the C double quotes in the value and protect them from the shell: gcc '-DVERSION="1.4.0"' main.c. In a Makefile write -DVERSION=\"$(VERSION)\" or use the stringizing operator in the code.
What is the MSVC equivalent?
cl /DNAME=value, which works the same way. CMake hides the difference: target_compile_definitions(app PRIVATE NAME=value) generates -D for GCC and Clang and /D for MSVC.