介绍 MSVC 的编译命令/ Zc
要深刻理解 MSVC 的 /Zc 编译命令,我们必须先聊聊微软一段长达数十年的“黑历史”。
在 20 世纪 90 年代到 21 世纪初,微软的 Windows 帝国如日中天。当时微软的 Visual C++ 编译器(MSVC)为了追求编译速度、契合 Windows 底层的特定 API,加之当时国际标准(C++98)本身也不够完善,微软在编译器中引入了大量独创的、不符合国际标准的“魔改语法”和特权。
比如:允许非左值引用绑定到临时变量、不严格区分嵌套模板的空格、老旧的宏预处理器等。
然而,随着 C++11 开启了现代 C++ 的大航海时代,国际标准变得极为严苛,GCC 和 Clang 编译器因严格遵守标准而迅速崛起。此时,微软面临一个巨大的两难境地:
- 如果直接强行把编译器修改为“完全符合国际标准”:全球无数依赖 VS 的企业老旧项目(如银行、军工、大型工业软件)在升级 VS 版本时会面临毁天灭地式的报错,导致无法编译。
- 如果继续纵容这些非标准 Bug:现代 C++ 的高端特性(如泛型编程、元编程、C++20 模块)将彻底无法在 Windows 上运行。
为了解决这个历史包袱,微软精心设计了一个**渐进式的“拨乱反正”开关——/Zc**。
1. 什么是 /Zc?
正如前面提到的,/Zc 是一个组合前缀:
Z:MSVC 中关于语言特性、代码生成的系列开关前缀。c:代表 Conformance(符合性),即“向 C++ 国际标准看齐”。
它的语法规则是:/Zc:特性名称[+|-](不加符号默认等同于 +)。
/Zc:特性:开启严格标准模式。哪怕会让你的老代码报错,也要强制按国际标准编译。/Zc:特性-:关闭严格标准,退回到微软历史上的“纵容/魔改”模式,以保证老代码能跑通。
2. 核心且常用的 /Zc 选项大盘点
随着 Visual Studio 2022 到如今最新的 Visual Studio 2026 的演进,许多 /Zc 开关已经默认开启,但了解它们依然是进阶 C++ 工程师的必修课。以下是开发中最常用、最核心的选项:
1) /Zc:__cplusplus(强烈推荐手动开启)
- 微软历史包袱:默认情况下,无论你把 C++ 标准设为 C++17 还是 C++20,MSVC 的
__cplusplus宏永远固执地输出199711L(C++98 的版本号)。这是为了防止极其古老的 Windows SDK 报错。 - 开启后的改变:强制让
__cplusplus宏正确输出当前标准的版本号(如 C++17 输出201703L)。 - 为什么重要:几乎所有现代跨平台开源库,都会通过这个宏来判断当前编译器支持什么特性。不开启它,第三方库常会误判。
2) /Zc:preprocessor(现代 C++ 必备)
- 微软历史包袱:老版 MSVC 拥有一个充满 Bug、不符合标准的宏预处理器,无法正确展开变参宏,嵌套宏逻辑混乱。
- 开启后的改变:废弃旧预处理器,启用全新的、完全符合 C++ 国际标准的预处理器。
- 为什么重要:编写 C++20 模块、使用高级元编程库(或像 Nana、ImGui 这种深度依赖宏机制的库)时,开启它可以避免莫名其妙的宏展开错误。
3) /Zc:char8_t-(带减号,向下兼容利器)
- 标准演进背景:C++20 引入了严格的
char8_t类型用于 UTF-8 字符串。这导致旧代码中所有的u8"hello"无法再隐式转换或赋值给普通的std::string或char*。 - 带减号的作用:禁用这一严格限制。在 C++20 环境下,依然允许将 UTF-8 字符串当做普通
char类型处理。 - 为什么重要:当你用 C++20 编译一个还没来得及完全适配 C++20 的第三方旧库(如之前的 Nana 库报错)时,这是最快的救场手段。
4) /Zc:wchar_t(已默认开启,历史意义重大)
- 微软历史包袱:在最早期的 Windows 编程中,微软为了图省事,把宽字符类型
wchar_t直接定义为了unsigned short(无符号短整型)的别名(通过typedef)。 - 开启后的改变:将
wchar_t恢复为 C++ 国际标准中规定的独立原生内置类型。 - 为什么重要:如果设为
-,void foo(wchar_t)和void foo(unsigned short)就会被视为重复定义,导致严重的函数重载冲突。
5) /Zc:inline(优化编译期和程序体积)
- 微软历史包袱:传统的 MSVC 比较纵容。如果你在头文件里声明了一个
inline函数,但其实全篇代码都没去调用它,MSVC 依然会死板地把它编译进.obj文件里,最后加大.exe的体积。 - 开启后的改变:严格执行标准。如果一个
inline函数或模板函数未被使用,或者它没有定义,编译器会直接将其剔除。 - 为什么重要:可以显著加快链接速度,并让最终生成的二进制文件更小、更干净。
3. 在现代开发中,应该如何对待 /Zc?
如果你在使用 CMake 进行现代 C++ 开发,微软其实已经意识到了天天让开发者手动配这些选项非常痛苦。
从近几年的现代 MSVC 开始,微软引入了一个“终极外挂”参数:/permissive-(意为“关闭宽容模式”)。
当你开启了 /permissive- 时,它会自动在幕后把绝大多数 /Zc 选项(如 /Zc:referenceBinding、/Zc:strictStrings 等)一键全部调成严格符合标准的状态。
因此,在现代 CMake 中,针对微软编译器的标准标准配置通常长这样:
if(MSVC)
# 1. 一键开启全面严格标准模式(相当于开启了一大堆 /Zc)
add_compile_options(/permissive-)
# 2. 依然需要手动补上的漏网之鱼(由于历史原因,/permissive- 没包含它们)
add_compile_options(/Zc:__cplusplus)
add_compile_options(/Zc:preprocessor)
endif()
通过这一套组合拳,你就把原本满是历史包袱的 MSVC,彻底伪装成了一个像 GCC 和 Clang 一样纯正、现代的、无 Bug 的标准 C++ 编译器!
小花絮 permissive
1. permissive 的音标与本义
- 英音(UK):/pəˈmɪs.ɪv/
- 美音(US):/pɚˈmɪs.ɪv/
📖 词源与本义
该词源自拉丁语动词 permittere(允许、放任),在日常英语中它是一个形容词:
- 字面本义:“放任的、纵容的、宽容的、听之任之的”。
- 日常语境:通常用来形容一种管理风格或社会风气。例如,permissive parents 指的是“对孩子百依百顺、不加管束的父母”;a permissive society 指的是“道德行为极其开放、自由放任的社会”。
2. 在上文(MSVC 编译器)中的具体含义
在微软 MSVC 编译器的语境下,/permissive 被借用为一个技术术语,用来形容编译器的“宽容度”。
🟢 /permissive(默认或老版本状态):宽容模式
- 含义:此时编译器就像一个“睁一只眼闭一只眼的老好人老师”。
- 表现:即使你的代码违反了现代 C++ 国际标准的规定,但只要它是历史上 Windows 特有的写法,或者属于某种早期的非标准 Bug 语法,编译器都会选择纵容它、放过它,允许它编译通过而不报错。
🔴 /permissive-(末尾带减号):关闭宽容模式(即:严格标准模式)
- 含义:我们在上文中用到的
/permissive-带有减号,表示“封杀/关闭这种纵容的态度”。 - 表现:编译器瞬间变成了一个“严厉、死板的国际考官”。它会剥夺 MSVC 历史上的所有“特权”和“纵容”,完全按照 C++ 国际标准(ISO C++)的法律法规来逐行审查你的代码。任何投机取巧、不符合跨平台标准的语法都会被直接无情地判定为严重错误(Error)。
总结
微软引入 /permissive- 这个名字非常带有自嘲的幽默感。加了这个参数,就是命令编译器:“收起你过去几十年对 Windows 老旧代码的‘溺爱与纵容’,给我按照国际标准严格执法!”。

浙公网安备 33010602011771号