Skip to content

/flags/c/gcc-d-define-macro

gcc -D NAME=value: define a preprocessor macro from the command line

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash)updated
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:

bash
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 && ./app3

Output

== 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 3

Without 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

OptionEquivalentNotes
-DNAME#define NAME 1Space after -D is also accepted: -D NAME
-DNAME=value#define NAME valueValue is taken literally as tokens
-DNAME=#define NAMEDefined 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 macroQuote it for the shell
-UNAME#undef NAMEAlso removes predefined macros
-DNDEBUG#define NDEBUG 1Disables assert() from assert.h

Typical uses

bash
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 macros

Pitfalls

  • A macro defined on the command line changes the meaning of every file compiled with it. Feature-test macros such as _GNU_SOURCE must 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 a macro redefined warning; guard defaults with #ifndef, as VERSION does above.
  • Build systems may cache objects: after changing -D flags, rebuild everything (most generators track flags, a hand-written Makefile often does not).
  • Use -D for 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.

Sources