MinGW、MSVC、CMake、QMake——这四个词放到一起基本就是 Windows 上做 C/C 开发的第一道坎。我见过不少新手装好了 Qt、写好了代码结果卡在编译器选择上点几下就出来一大堆看不懂的报错。这篇内容不是我翻译文档而是把自己踩过的坑、总结过的选型思路一次性讲清楚。文章会从最本质的编译器区别讲起再带你走一遍 CMake 和 QMake 的实际配置流程最后给出一套能直接套用的决策表。无论你是刚接触 Windows C/C 的新手还是被工具链折磨过的老手应该都能找到点有用的东西。1. 工具链全景编译器与构建系统到底是什么1.1 编译器MinGW 和 MSVC 的本质区别编译器是整个工具链的基石。同样是main.cpp文件MinGW 和 MSVC 最终都能产出.exe但中间过程和产物差异非常大。MinGW 的全称是 Minimalist GNU for Windows它是 GCC 编译器在 Windows 平台上的移植保留了 Linux 上常见的gcc、g命令风格也支持make体系。现在的主流版本是 MinGW-w64它支持 64 位编译比老版 32 位 MinGW 活跃得多官方下载入口一般在 SourceForge 或 winlibs 等镜像站安装时还要注意选 POSIX 线程模型还是 Win32 线程模型。MSVC 就是 Microsoft Visual C/C对应 Visual Studio 里那个cl.exe编译器。它的优势在于和 Windows SDK、调试器、性能分析器深度集成很多微软生态的库默认就是按 MSVC 编译的。cl.exe通常不在系统 PATH 里需要在“开发人员命令提示符”或 Visual Studio 安装目录里调用这也是很多新手第一次用命令行编译时碰壁的地方。两者的区别不只是“能编译就行”。它们用的 C 标准库实现不同MinGW 用 libstdcGCC 自带MSVC 用 Microsoft STL。函数名修饰Name Mangling规则也不同这意味着 MinGW 编译出来的.o文件和 MSVC 编译出来的.lib根本无法互相链接即使接口完全一样。这一点在后续遇到链接错误时尤其致命所以我建议从源头上就统一工具链不要混用。1.2 构建系统CMake 和 QMake 的角色定位编译器只负责“把代码变成机器码”但它不知道怎么组织几十个源文件、怎么处理依赖库、怎么选择宏定义。这就是构建系统要干的事。CMake 是一个跨平台的“构建系统生成器”它读CMakeLists.txt配置文件然后生成当前平台能用的构建文件比如 Linux 上的 Makefile、Windows 上的 Visual Studio Solution或者更高效的 Ninja 文件。CMake 自己并不直接编译代码真正的编译还是由gcc、cl或ninja调用编译器完成。QMake 是 Qt 官方早期推出的构建工具读.pro和.pri文件为 Qt 项目服务。它最大的特点是天然理解 Qt 的元对象编译机制比如自动处理 moc、uic、rcc 这些步骤写 Qt 项目时非常省心。但它的通用性不如 CMake遇到大型跨平台项目、复杂第三方依赖时.pro文件的表达能力会有点捉襟见肘。Qt 官方也早已意识到这个问题Qt 6 时代官方推荐的新项目一律使用 CMakeQMake 基本处于维护状态。这里有一张简单的对比表方便快速建立印象维度CMakeQMake配置文件CMakeLists.txt.pro / .pri适用范围不限制语言和框架主要围绕 Qt 项目跨平台能力可生成 Makefile、Ninja、VS 工程可生成 Makefile 或 VS 工程但依赖 Qt第三方库查找find_package很强较弱常需要手动指定路径Qt 官方态度新项目首选遗留项目维护1.3 CMake 与 Makefile 的区别以及它们如何一起工作很多人会困惑CMake 和 Makefile 到底是不是一回事其实它们是两个层级的东西。Makefile 是传统构建脚本由 make 程序解析里面规定了“要编译哪个文件、用什么参数、先做什么后做什么”。手写 Makefile 对于简单项目还能应付一旦项目变大跨平台要求提高手写就成了灾难。比如同一个库在 Linux 用.so在 Windows 用.dll源文件可能还依赖编译器类型这些东西写死在 Makefile 里会非常痛苦。CMake 是“生成 Makefile 的更高层工具”。你写的是声明式的CMakeLists.txt描述“项目叫什么、要生成什么目标、链接哪些库”CMake 根据当前系统和生成器生成对应的 Makefile、Ninja 文件或 Visual Studio 工程。也就是说你只需要维护一份 CMakeLists.txt就能在多个平台构建这就是它存在的核心价值。QMake 也类似它读.pro文件后生成 Makefile只是生成的内容里附加了大量 Qt 相关的规则。理解了 CMake 和 Makefile 这层关系再看 QMake 就顺了QMake 也是“生成器”只不过更偏向 Qt 生态。整个构建流程分成两个阶段配置configure和构建build。配置阶段 CMake 会检查编译器、依赖库、参数选项然后生成构建文件构建阶段才真正调用编译器去编译。所以如果你改了 CMakeLists.txt必须重新跑 configure如果只改.cpp源码直接 build 就行。这个“两段式”思维是后面排查一切问题的基石。2. 核心场景拆解什么时候选 MinGW什么时候选 MSVC2.1 跨平台开源项目偏好 MinGW 的原因如果你从 Linux 迁移一个开源项目到 Windows大概率会倾向 MinGW。为什么因为项目里的编译参数很可能都是 GCC 风格比如-fPIC、-Wall、-stdgnu17MSVC 的cl.exe对这些参数完全不认识你需要改一堆编译配置。而 MinGW 几乎就是“在 Windows 上假装 Linux”GCC 的命令行参数、常见宏定义都能无缝衔接。另外很多用 autotools 或 CMake 维护的开源库在 Windows 上用 MinGW 构建时更容易通过。因为它们在configure阶段检测编译器的逻辑就是按 GCC 来写的。如果你强行用 MSVC还得处理“某个宏被定义成不同值”“某个扩展语法 MSVC 不支持”之类的问题。不过要注意MinGW 不是 Cygwin。Cygwin 是模拟了完整的 POSIX 环境生成的程序依赖 cygwin1.dllMinGW 直接调用 Windows API生成的是地道的 Windows 原生程序只依赖少量 GCC 运行时 DLL如 libstdc-6.dll。所以它在“能跑”和“跨平台”之间找了一个很好的平衡点。2.2 MSVC 在 Windows 原生生态的优势如果项目只面向 WindowsMSVC 往往是最优解。首先Visual Studio 的调试器对 MSVC 编译的代码体验极佳断点、内存窗口、异常可视化、性能分析都不是第三方工具随便能替代的。其次Windows 平台独占的 API、驱动开发、UWP、DirectX 相关项目官方文档和示例默认都是 MSVC 环境。第三微软的 C 标准库STL更新非常积极对 C20/23 的支持往往是最快的。还有一个很现实的问题商业第三方库。很多收费或闭源的库只发布 MSVC 编译好的.lib和.dll比如某些指纹识别 SDK、工业控制通信库。你选了 MinGW可能连链接都过不了只能求厂商重新编译或者换库。因此决定工具链之前一定要先查清楚核心依赖有没有对应版本。2.3 Qt 开发中的 MinGW 与 MSVC 之争在 Qt 社区MinGW 和 MSVC 之争几乎是个月经话题。Qt 官网下载页面会提供两种常见的预编译包一种叫mingw_64另一种叫msvc2019_64或msvc2022_64。选哪个主要看你的其他依赖是谁编译的。如果你打算用 vcpkg 安装第三方库默认行为是用 MSVC 编译那你的 Qt 也应该选 MSVC 版本否则库的二进制格式不一致。如果你习惯用 Qt 自带的 MinGW 工具链那么所有第三方库最好也能找到 MinGW 版本或者干脆自己用 MinGW 源码编译。还要特别留意架构匹配。Qt Creator 里的 Kit 并不只是选编译器那么简单它会把编译器、调试器、Qt 库版本、CMake/QMake 路径绑定在一起。你得确保编译器是 64 位你选的 Qt 也是 64 位否则会出现头文件能找到链接时一堆符号找不到的诡异错误。2.4 工具链与运行库的兼容性问题二进制兼容是 Windows C/C 开发里最容易踩的暗坑。即使你把源码编译通过了程序运行时也可能在内存释放的一瞬间崩溃。原因很简单MinGW 的 libstdc 和 MSVC 的 STL 对std::string内部结构、new/delete的实现方式完全不同。一个库分配的内存被另一个工具链的代码释放直接破坏堆。即使使用 C 接口extern C规避函数名修饰也无法避免 CRT 运行库不一致的问题。MSVC 本身还分/MD动态链接和/MT静态链接Debug 用/MDd、Release 用/MD混合使用同样可能出错。所以我的建议是整个依赖链尽量统一工具链统一运行库参数不要因为某个预编译库方便就引入不匹配的怪东西。3. CMake 实战从配置到生成解决常见坑3.1 安装与基础配置先解决怎么装上 CMake。Windows 下最简单是去官网下载.msi安装包安装时注意勾选“把 CMake 加入 PATH”这样命令行里就能直接敲cmake --version。如果你喜欢绿色版也可以下载 ZIP 包解压后把里面的bin目录手动加到环境变量效果完全一样。Visual Studio 安装器里也带 CMake 组件叫“适用于 Windows 的 C CMake 工具”装了之后 VS 项目里可以直接用。Ubuntu 下一般执行sudo apt install cmake就行但这个源里的版本通常比较旧。比如 Ubuntu 18.04 自带的是 3.10而很多现代项目要求 CMake 3.16 甚至更高。遇到这种情况要么用官方源加 Kitware 仓库要么下载新版压缩包手动安装后面 3.5 节我会专门说离线安装的办法。3.2 一个最小示例CMakeLists.txt 写什么直接给一个能跑的模板。假设目录结构是demo/ CMakeLists.txt main.cppmain.cpp随便写个 hello world。CMakeLists.txt内容cmake_minimum_required(VERSION 3.16) project(Demo LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_executable(demo main.cpp)然后执行两个命令构建cmake -S . -B build和cmake --build build。第一行命令中的-S .表示源码目录是当前目录-B build表示构建目录是build配置结果和中间文件都会放到这里不会污染源码。第二行命令会根据之前生成的构建文件执行真正的编译。这种“源码目录和构建目录分离”的做法可以避免生成的各种临时文件混在源码里强烈建议从一开始就习惯它。3.3 切换生成器指定 MinGW Makefiles 或 Visual Studio在 Windows 上CMake 默认可能会检测到 Visual Studio然后自动选择 Visual Studio 生成器。这通常没问题但如果你的环境里没有装 VS只装了 MinGWCMake 就会报错说找不到编译器。解决办法是显式指定生成器。假设你已经把 MinGW-w64 的bin目录加进 PATH并且能运行g --version和mingw32-make --version那么用这个命令cmake -S . -B build -G MinGW Makefiles如果 CMake 还是找不到编译器可以再显式指定cmake -S . -B build -G MinGW Makefiles -DCMAKE_C_COMPILERgcc -DCMAKE_CXX_COMPILERg另一种常见情况是你想用 Visual Studio 生成器但是希望命令行构建时能指定 Debug/Release。使用 VS 生成器时配置阶段不会直接产生 Makefile而是生成一个.sln文件。构建的时候不能简单make需要用cmake --build build --config Release这里的--config Release对 VS 多配置生成器是必需的因为 Debug 和 Release 配置是在构建阶段生成的。3.4 常见错误解析CMake Error: The following variables are used...这个错误在配置阶段经常出现完整提示类似CMake Error: The following variables are used in this project, but they are set to NOTFOUND. Please set them or make sure they are set and tested correctly in the CMake files: XXX_LIBRARY翻译成人话就是你在 CMakeLists.txt 里引用了一个变量但它没有值最终变成了XXX_LIBRARY-NOTFOUND。最常见的是编译器变量比如CMAKE_C_COMPILER找不到也有可能是某个依赖库的变量没找到。排查步骤有规律看变量名。如果是CMAKE_C_COMPILER、CMAKE_CXX_COMPILER先检查编译器是否安装、是否在 PATH 里。如果是FOO_LIBRARY、FOO_INCLUDE_DIR说明find_package(FOO)或find_library(FOO)失败了你需要用-DFOO_LIBRARY具体路径手动指定。打开 build 目录里的CMakeCache.txt搜索变量名看看有没有残留的错误路径。缓存会把上一次配置的结果记下来改环境后不删缓存会继续报错。最朴素但有效的办法把 build 目录整个删掉重新配置。很多“玄学”问题其实是缓存搞鬼。举例Qt 6 项目报Qt6_DIR-NOTFOUND你就要去找 Qt 安装目录下的lib/cmake/Qt6路径然后加-DQt6_DIRC:/Qt/6.5.3/msvc2019_64/lib/cmake/Qt6。找到这个门路之后类似的问题都可以举一反三。3.5 离线安装 Ubuntu 上 CMake 的经验很多生产环境是内网隔离的不能apt-get install但项目又需要高版本 CMake。这时候可以提前在能联网的机器上下载官方预编译包比如cmake-3.27.9-linux-x86_64.tar.gz用 U 盘或者内部服务器拷贝进去。安装步骤sudo tar -zxvf cmake-3.27.9-linux-x86_64.tar.gz -C /opt sudo ln -s /opt/cmake-3.27.9-linux-x86_64/bin/cmake /usr/local/bin/cmake cmake --version这一步做完终端一般就能识别 CMake。如果提示缺少libssl.so.1.1之类的说明系统里没装对应依赖需要先通过内网软件仓库或离线 rpm/deb 包把libssl-dev装上。官方预编译包基本依赖很弱但少数字典库还是需要提前准备。Ubuntu 18.04 用户尤其要注意 apt 源里的 CMake 太老某些 CMakeLists.txt 里用了 3.16 的语法会直接报语法错误第一反应不是改代码而是升级 CMake。离线方式是最稳妥的不会像 apt 源那样搞乱系统其他部分。3.6 CMake GUI 到底怎么用很多人第一次接触 CMake 是在图形界面里。CMake GUI 的作用跟命令行一样只是把变量展示在表格里。你只需要选择源码目录和构建目录点 Configure它会弹出生成器选择窗口选好后加载配置红色高亮的变量就是需要你填写的比如编译器路径、依赖库路径。填完再点 Configure所有变量不再是红色就可以点 Generate 生成了。GUI 的优势是直观适合新手熟悉有哪些变量。但实际工程里我还是推荐命令行因为命令行可以形成可重复执行、可备份的脚本而 GUI 点完就没了下次换台机器还得重新点一遍。4. QMake 实战Qt 项目的构建与切换4.1 QMake 与 CMake 在 Qt 项目中的分工很多人印象里 Qt 项目必须用 QMake其实不对。QMake 只是 Qt 官方早期的选择现在新项目用 CMake 反而是更“官方”的路线。但很多遗留项目、老教程依然在用.pro文件所以你至少要去了解它。一个典型的.pro文件就是这么简单直白QT widgets TARGET demo SOURCES main.cpp widget.cpp HEADERS widget.h在 Qt 安装目录的bin下找到 qmake然后执行qmake生成 Makefile再执行make或nmake。QMake 会自动为 Qt 元对象系统生成 moc 文件不需要你在 Makefile 里手动写那套复杂规则这对纯新手特别友好。但 QMake 的问题也在这个“自动”。一旦项目要接入非 Qt 的库要在.pro里手动写LIBS -lxxx、INCLUDEPATH ...没有一个标准化的查找机制。而 CMake 的find_package可以自动处理很多依赖关系所以大型项目几乎都会选择 CMake。4.2 在 Qt 中使用 MSVC 编译器的配置当你在 Qt 官网上看到msvc2019_64这样的安装包时意思是这个 Qt 库是用 MSVC 2019 64 位编译的。如果你希望自己的工程也基于 MSVC需要先确保本机装了 Visual Studio 或 Build Tools for Visual Studio并且安装“使用 C 的桌面开发”工作负载。打开 Qt Creator菜单“工具 - 选项 - Kits - 编译器”点击“添加”按钮选择“Microsoft Visual C Compiler”Qt Creator 会自动扫描 VS 安装目录找到cl.exe。然后在“Kits”里新建一个套件或修改默认套件Qt 版本选择 MSVC 对应的那个编译器选择刚添加的 MSVC最后把“CMake Generator”或“QMake”配置为对应项目类型。如果编译时提示找不到cl.exe多半是 Qt Creator 没有自动加载 VS 的环境变量。在 Kits 设置的“环境”一栏可以手动添加vcvarsall.bat x64的调用命令也可以直接点“使用此套件时先在终端中运行”的相关选项具体版本略有差异搜索一下并不难。要记住一个最核心的原则Qt 库是什么编译器编译的你的项目就要用什么编译器。MinGW 的 Qt 包配 MSVC 编译器或者反过来都会在链接阶段报一大堆无法解析的外部符号。4.3 QMake 与 CMake 的互转思路有时候你拿到一个老工程是.pro文件但你想统一到 CMake。不建议把.pro一行一行翻译而是重新理解项目结构。比如开头那个简单的.pro文件用 CMake 写就是这样cmake_minimum_required(VERSION 3.16) project(demo VERSION 1.0 LANGUAGES CXX) find_package(Qt6 REQUIRED COMPONENTS Widgets) qt_standard_project_setup() qt_add_executable(demo main.cpp widget.cpp widget.h ) target_link_libraries(demo PRIVATE Qt6::Widgets)区别在哪里.pro里的SOURCES和HEADERS在 CMake 里都可以直接放进qt_add_executableQt 的 CMake 函数会自动区分头文件和源文件并处理 moc。这比手动写set(CMAKE_AUTOMOC ON)更省心因为qt_add_executable已经给你封装好了。反向从 CMake 转 QMake 就麻烦很多毕竟现代 CMake 的依赖图比.pro要复杂QMake 表达不了很多概念。所以新项目应以 CMake 为主老项目在没有强理由的情况下也不要强行迁移。4.4 常见坑编译器版本、架构 mismatch 等我在 Qt 项目里见过最多的报错有两类。第一类Qt Creator 里选择的是 MSVC 套件但实际 CMake 却找到了 MinGW 的 g配置阶段报错“CMAKE_CXX_COMPILER 不是 MSVC”。原因是套件里的 C 编译器项配错了改成 MSVC 后重新配置即可。第二类库和 Qt 架构不匹配。比如你装的 Qt 是 64 位 MSVC 2019但某个三方库是 32 位 MinGW 编译的链接时会出现invalid or corrupted file之类的提示。这没有任何技巧可绕只能重新找匹配的库或者自己用工具链重新编译。还有一类 Debug/Release 问题。MSVC 下 Debug 动态库是Qt6Cored.dllRelease 是Qt6Core.dll。程序调试时加载错了 DLL启动直接报“找不到 Qt6Cored.dll”。解决办法就是确保 PATH 里 Qt 的bin目录正确Debug 模式必须能访问到带 d 的 DLL。5. 选型建议与避坑心得5.1 快速决策表我一直跟身边朋友讲工具链没有绝对优劣只有适不适合。下面这张表是我自己常用的快速决策表应用场景推荐工具链核心理由纯 Windows 原生 GUI 应用MSVC CMake Ninja调试器强微软生态友好跨平台开源库/CPU密集项目MinGW/GCC CMake参数兼容迁移成本低需要大量 MSVC 预编译库MSVC CMake二进制兼容性是硬门槛Qt 项目面向 Windows 发布MSVC 版 Qt MSVCvcpkg 依赖更顺畅Qt 项目快速验证原型MinGW 版 Qt QMake简单省事不用配置 VS命令行跨平台构建CMake Ninja比 Makefile 快比 VS 工程轻5.2 我踩过的坑第一个坑是装了多个 MinGW一个 32 位一个 64 位PATH 里 32 位的在最前面。CMake 自动检测时找到了 32 位 gcc编译通过但运行就崩查了半天才发现架构不对。最后统一删掉多余版本只保留 64 位 MinGW-w64。第二个坑是配置 CMake 时只设了CMAKE_CXX_COMPILER忘了CMAKE_C_COMPILER。很多库在检测依赖时需要测试 C 编译器找不到就判断为不支持。后来学乖了新建工具链时两个一起指定。第三个坑在 Qt 静态编译尝试中。我在.pro里写了CONFIG static结果链接时疯狂报 unresolved external symbol。后来发现这个 Qt 库是动态编译的不配合静态编译版本就没有.a或.lib可用。这种问题基本无解要么换库要么放弃静态编译。第四个坑是 Debug 和 Release 库混用。MSVC 的 Debug 默认链接MDd运行库如果某个第三方 lib 是MDRelease编译的就会在运行时出现奇怪的内存错误。配置没有捷径只能保证所有参与链接的库 Release/Debug 属性一致。5.3 实用小技巧这里分享几个我一直用的命令行小技巧。用cmake --build替代直接调用 make 或 ninja。它会根据生成的构建文件自动选择正确的构建工具不需要你在命令行里纠结是mingw32-make还是nmake。想要并行编译可以加-j参数cmake --build build -j 8需要提速时CMake 生成器选 Ninja。Ninja 是专门为快速构建设计的增量编译速度明显优于 Makefile。Windows 和 Linux 都能用只要装了 ninja命令是cmake -S . -B build -G Ninja。在 Windows 上用 MinGW 时安装 MinGW-w64 优先选 POSIX 线程模型的发行版因为 Win32 线程模型不支持 C11 的std::thread一编译就报错。很多初学者在不知情的情况下装错版本直接卡在“为什么我的线程库编译不了”。选择时注意看安装包名称通常标注posix的就是正确选择。遇到任何“改了源码但没生效”的诡异情况先删掉 build 目录重新 configure。因为 CMakeCache.txt 里可能缓存了旧编译器路径、旧库路径这些比源码错误更隐蔽。另外如果你是 MATLAB 用户要用 MinGW-w64 编译 MEX 文件记得去 MathWorks 官方查看版本支持时间表。MATLAB 对 MinGW 的版本有严格限制装得太新反而mex -setup识别不到这个问题不在少数。5.4 高频搜索问题的精简回答很多人会直接在网上搜“msvc和mingw区别”“cmake和makefile区别”“qt怎么使用msvc”这类问题这里用一句话给出关键答案高频问题一句话回答msvc和mingw区别是什么编译器不同ABI、标准库、链接库格式都不一样不能混用cmake和makefile区别CMake 生成 MakefileMakefile 是给 make 用的构建脚本cmake下载应该去哪官网下载对应平台安装包或压缩包Windows 用 MSILinux 用 tar.gzqt怎么使用msvc安装 MSVC 版 Qt并在 Qt Creator 里添加 MSVC 编译器套件ubuntu cmake 离线安装怎么搞官网下载 tar.gz解压到 /opt软链到 /usr/local/bincmake error variables NOTFOUND 怎么办用 -D 手动指定变量路径或删除 build 目录重新配置这些问题的底层逻辑其实就是本文前面讲的那些原理。理解了编译器、构建系统、工具链匹配的关系网上那些碎片化答案都不难理解。我个人在实际操作中的体会是工具链这个事讲究“一条路走到黑”。开工前先想清楚目标平台和依赖库选择一条主路径然后所有环节都跟着它走。最怕的就是 A 依赖用 MSVCB 依赖用 MinGW项目明明不大却被链接错误拖了一天。希望这份折腾经验能让你少走点弯路。