最可靠方式是运行gcc -dM -E -
查看GCC内置宏定义的完整命令
直接运行
gcc -dM -E - 就能列出所有默认宏,这是最可靠、最轻量的方式。它不依赖任何源文件,也不触发编译,只让预处理器输出当前环境下的全部有效宏定义。常见错误是漏掉
-(表示从标准输入读取)或写成gcc -dM -E /dev/null——后者会报错,因为/dev/null是空文件,预处理器无法从中读取内容;而- 明确告诉 GCC 从 stdin 读,且 stdin 被重定向为空,正好触发“仅输出内置宏”的行为。
-dM必须和-E配合使用,单独用-dM会被忽略- 输出包含
__linux__、__x86_64__、__SIZE_TYPE__等平台/架构/类型相关宏,也包含__STDC_VERSION__这类标准版本宏- 如果想过滤特定宏,比如查 32 位整数相关定义,可以接
| grep INT32查看带自定义宏的完整宏列表
要同时看到内置宏 + 你用
-D手动加的宏,不能只靠-dM -E - ,因为它不加载你的定义。正确做法是:把一个空的或最小化的 C 文件(比如只写int main(){})和-D一起送进预处理流程。例如,你想确认
DEBUG和VERSION=2是否生效:echo 'int main(){}' | gcc -DDEBUG -DVERSION=2 -dM -E -这样输出里就会同时包含
__DEBUG__(GCC 自动补的)、DEBUG、VERSION以及所有内置宏。
- 不要用
gcc -DDEBUG -dM -E - ——/dev/null会导致-D定义被忽略(GCC 在无输入时跳过宏注入逻辑)echo 'int main(){}'提供合法的 C 入口点,确保预处理器正常工作,又不会引入额外头文件干扰结果- 如果源文件本身有
#define,那它的宏也会出现在最终输出里,但-dM只输出“预处理结束时仍有效的宏”,所以被#undef掉的不会出现调试宏展开时该用哪个命令
查“定义了哪些宏”和查“某个宏实际展开了什么”是两件事。
-dM只管定义,不管展开;要看展开效果,得用-E输出预处理后的代码。比如你写了
#define SQUARE(x) ((x)*(x)),想知道SQUARE(a + b)到底变成啥,就该:echo '#define SQUARE(x) ((x)*(x))\nint y = SQUARE(a + b);' | gcc -E -输出里你会看到
int y = ((a + b)*(a + b));——这才是真实替换结果。
-E默认不输出宏定义列表,只做文本替换;加-dM才会混着输出定义+展开,容易混淆,不推荐合用- 如果源文件较大,用
gcc -E main.c > main.i再人工搜索目标宏更清晰- 注意:
-E输出里所有#line指令都是为调试服务的,别被它们干扰对宏展开的判断为什么
gcc --version不能代替-dM
gcc --version只告诉你编译器版本号,完全不涉及宏。而不同 GCC 版本、不同 target 架构、不同 C 标准(-std=c17vs-std=gnu11)会导致内置宏差异极大。比如__STDC_VERSION__在-std=c99下是199901L,在-std=c17下是201710L,这些只有-dM能准确反映。真正影响代码行为的是宏定义本身,不是编译器名字或数字版本。
- 交叉编译时尤其关键:同一台机器上跑
arm-linux-gnueabihf-gcc -dM -E - 和x86_64-linux-gnu-gcc -dM -E - 输出几乎完全不同- 某些宏(如
__LP64__)只在 64 位 ABI 下定义,--version完全看不出这个信息- 如果你在条件编译里用了
#ifdef __clang__,那必须确认 GCC 确实没定义它——光看版本号毫无意义实际用的时候,多数人只记住第一行就够了:
gcc -dM -E - 。但一旦涉及自定义宏或需要验证展开逻辑,就得切换命令组合,而且顺序和参数搭配稍有差池,结果就不可信。












