C++标准不规定宏名,各编译器自行定义且值不可靠;应优先用编译器专属宏(如__clang_major__)、标准特性宏(如__cpp_lib_span)或构建系统实测,而非硬背版本对照表。

没有“完整版本对照汇总表”这种东西——C++ 标准不规定宏名,各编译器自行定义,且同一编译器不同版本、不同平台(如 Windows/Clang on macOS/MinGW)的宏值可能完全不同。硬背表格只会踩坑。
为什么 __GNUC__ 不能直接当 GCC 版本号用
很多代码写成 #if __GNUC__ >= 12,看似在判断 GCC 12+,但这是错的:__GNUC__ 只是主版本号,__GNUC_MINOR__ 和 __GNUC_PATCHLEVEL__ 才构成完整版本。Clang 也定义了这三个宏(为了兼容),但它们反映的是 Clang 声称“兼容的 GCC 版本”,不是 Clang 自身版本。
常见误判现象:
- Clang 16 编译时,
__GNUC__ == 14(它声称兼容 GCC 14),__GNUC__ >= 12成立,但你本意可能是“只对真实 GCC 12+ 启用某特性” - Intel ICC 或 NVCC 也可能定义这些宏,值更不可靠
正确做法是优先用编译器专属宏:
- 区分 GCC 真实存在:
#ifdef __GNUC__ && !defined(__clang__) && !defined(__INTEL_COMPILER) - 获取真实 GCC 版本:
(__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) - Clang 自身版本用
__clang_major__、__clang_minor__、__clang_patchlevel__
_MSC_VER 对应 MSVC 版本不是线性映射
_MSC_VER 是微软的内部编码,不是“Visual Studio 2022 = 1930”这种直觉对应。比如 VS 2022 17.0 的 _MSC_VER 是 1930,但 17.4 是 1934,17.9 是 1939 —— 中间跳过 1935~1938。VS 2019 16.11 是 1929,16.0 是 1920。
关键点:
-
_MSC_VER只随 MSVC 工具集(toolset)变,和 VS IDE 版本号无关;同一 VS 安装可含多个 toolset(如 v142/v143/v144),对应不同_MSC_VER - 不要用
_MSC_VER / 10算“大版本”,1930 / 10 = 193 ≠ VS 2022(它是 17.x) - 查证必须看 Microsoft 官方文档《Predefined Macros》中 “_MSC_VER” 表格,而非第三方整理的“换算口诀”
示例:检测是否为 MSVC 19.30+(即 VS 2022 17.0+):#if defined(_MSC_VER) && _MSC_VER >= 1930
跨平台项目里该用哪个宏判断 C++ 标准支持
想用 std::span?别查编译器版本,查标准特性宏:__cpp_lib_span。C++20 起,各编译器对标准库特性的启用是独立控制的,GCC 11 支持 std::span 但默认不开启(需 -std=c++20 且未禁用),而 Clang 12 在 -std=c++20 下才定义 __cpp_lib_span。
可靠路径只有两条:
- 查标准特性宏(以
__cpp_或__cpp_lib_开头),它们由编译器在启用对应特性时自动定义 - 用
__cplusplus判断语言标准级别(注意:MSVC 长期错误地将__cplusplus固定为 199711L,直到 VS 2017 15.7+ 才修复;启用/Zc:__cplusplus才能获得正确值)
错误示范:#if __GNUC__ >= 10 → 假设 GCC 10 支持 std::format,实际 GCC 13 才完全支持,且需 -std=c++23。
真正该做的:把宏检查封装成可读的构建逻辑
手写一堆嵌套 #if defined(__clang__) && __clang_major__ >= 15 || defined(__GNUC__) && ... 不仅难维护,还容易漏掉 ICC、PGI、ARM Compiler 等小众编译器。
建议做法:
- 用 CMake 的
check_cxx_source_compiles()或try_compile()实际编译测试特性可用性,生成config.h头文件 - 或使用成熟的跨平台宏库(如 portable-snippets 中的
compiler.h),它已处理好 Clang/GCC/MSVC/ICC 的交叉定义歧义 - 业务代码里只依赖你自己定义的语义化宏,如
HAS_STD_SPAN,而不是裸写__cpp_lib_span
最常被忽略的一点:宏定义本身可能被命令行 -D 覆盖,或受 #pragma push_macro 影响。任何依赖宏的判断,都要在预处理后验证实际值(用 gcc -E 或 cl /P 查看展开结果)。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











