Windows下用MSYS2+GCC+CMake搭建轻量C++开发环境
2026/9/16 1:27:02 网站建设 项目流程

如果你也跟我一样,平时在 Windows 上写 C++,却又被 Visual Studio 的重量级体验折腾得够呛,那这篇内容大概率能帮到你。我用 MSYS2 搭配 GCC 和 CMake 在 Windows 上搭了一套很接近 Linux 的开发环境,日常写算法、跑开源项目、做课程作业都在这套环境里完成,已经稳定用了很长一段时间。这套方案对经常要跨平台编译、又不想在 Windows 和 Linux 之间来回切换的人来说,基本是性价比最高的选择。

先说清楚这套组合到底是什么:MSYS2 相当于一个运行在 Windows 上的“迷你 Linux 发行版”,自带 pacman 包管理器、bash 终端和大量 GNU 工具;GCC 是编译工具链核心;CMake 负责构建流程。三者配合下来,你不需要打开 Visual Studio,也不用忍受 MSBuild 的慢速构建,直接在终端里敲命令就能完成从编辑、编译到调试的全过程。如果你厌倦了 VS 安装器动辄几个 GB 的更新,或者被各种“Microsoft Visual C++ 14.0 or greater is required”这类报错折磨过,这篇文章就是为你准备的。我会把从零安装到项目实战的每一步写清楚,尤其是那些文档里根本不会写的坑,尽量让你少走弯路。

1. 为什么我建议你用 MSYS2 + GCC + CMake 替代 Visual Studio

1.1 Visual Studio 在 C++ 开发里的几个“重”与“痛”

Visual Studio 毫无疑问是 Windows 平台最强大的 C++ IDE,但对很多人来说它确实太重了。安装完基础 C++ 工作负载通常要占十几个 GB 的磁盘空间,启动和加载解决方案也偏慢;更麻烦的是,一旦项目需要引入开源库,MSVC 生态和 Linux 那边一大堆用 GCC 编译的库经常不兼容,要么自己拿 vcpkg 从头编译,要么就在源码移植上耗大量时间。

另一个常见的痛点就是 MSBuild 的构建速度。项目小的时候还好,一旦代码规模上来,VS 的增量构建经常让人盯着进度条怀疑人生。而 GitHub 上大量开源项目默认的构建方式都是 CMake + Makefile 或 CMake + Ninja,你在 Windows 上想用 Visual Studio 打开这些项目,还得先生成一堆 .sln 工程文件,过程繁琐且容易报错。

我并不是说 Visual Studio 不好,它有极其强大的调试器、代码分析和性能剖析工具,在 Windows 桌面应用、MFC、C++/CLI 这些领域依然是第一选择。但如果你主要写跨平台 C++、刷算法题、跑开源项目,或者只是需要一个轻量顺手的编译环境,那 MSYS2 + GCC + CMake 会让你舒服得多。

注意:如果你要做 Windows 驱动开发、依赖 MSVC 专有扩展,或者必须使用 MFC,那还是老老实实用 Visual Studio,没必要强行切换工具链。

1.2 MSYS2 的定位:不是“又一个 MinGW”,而是一个包管理系统

很多老开发者印象里的 MinGW 是一个简单的 GCC 移植包,装完之后只有一个编译器,要啥库都得自己去找。但 MSYS2 完全不是这个逻辑,它更像一个轻量级的 Linux 发行版,核心是 pacman 包管理器。你想要 GCC,一条pacman -S就装好;想要 Boost、OpenCV、fmt、SQLite 这些库,同样一条命令直接拉取预编译包,再也不需要像 vcpkg 那样源代码编译到天荒地老。

MSYS2 提供多个子环境,其中最常用的是 MINGW64、UCRT64、CLANG64。这些子环境唯一的区别在于底层运行时库和编译器版本。我个人的强烈建议是使用 UCRT64 环境,因为 Windows 10 以后系统本身就自带 UCRT(Universal C Runtime),用这个环境编译出来的程序不依赖额外的 VC++ 运行库,分发比较省心。

从使用体验来说,你在 MSYS2 里打开终端,就是面对一个 bash shell,可以敲lsgrepsedmake,各种操作和 Linux 几乎没有区别。如果你平时接触过 Ubuntu 或 CentOS,会觉得很亲切;就算没接触过,跟着这篇文章走一遍,很快也能习惯这种命令行工作流。

1.3 这套方案适合谁?又真的不适合谁?

根据我自己的经验,这几类人特别适合切换到 MSYS2 + GCC + CMake:

  • 学生党或刷题党:经常要编译 C++ 算法代码,VS 太大太重,这套方案秒开终端直接编译。
  • 开源项目玩家:大量 GitHub 项目使用 CMake 构建,用这套环境能 1:1 还原 Linux 下的构建体验。
  • 嵌入式开发者:需要交叉编译工具链,MSYS2 也能装 arm-none-eabi-gcc 这类工具。
  • 有 Linux 使用习惯但工作机是 Windows 的人:不用开虚拟机,不用双系统,就能享受 Linux 式命令行体验。

不适合的人也很明确:重度使用 Windows 专有 API、需要 MFC 界面、依赖 MSVC 特有语法或者需要 Windows 驱动开发环境的人,建议还是留在 Visual Studio 生态里。

2. 全套环境搭建:从 MSYS2 安装到编译器配置

2.1 下载安装 MSYS2,以及“卡在 50%”的经典问题

第一步去 MSYS2 官网下载安装器,这个一般不会有人搞错。但安装路径我要特别强调一下:不要使用默认的 C:\Users\你的用户名\AppData\Local\Programs\Msys64,直接改成 C:\msys64。原因是尽量保证路径中没有空格和中文,否则后续 CMake、Ninja、Make 这类工具在路径解析时偶尔会出现奇怪的问题,尤其是你在 VSCode 里配置 tasks 或 launch 的时候,路径带空格很容易踩坑。

安装过程本身很简单,一路 Next 就行。但很多人会碰到安装到 50% 左右卡住的情况,这个我后来才弄明白,多半是安装器在下载/初始化 pacman 基础包时网络不稳定或镜像源不可达导致的。解决办法分两种:

第一种比较简单,直接取消安装,把安装目录残留删除干净,重新运行安装程序。如果网络条件正常,通常第二次就能顺利装完。

第二种就是安装完成后打开 MSYS2 终端时发现更新卡住。这时候不要慌,先关掉所有 MSYS2 窗口,然后用文本编辑器直接编辑安装目录下的/etc/pacman.d/mirrorlist.ucrt64.txtmirrorlist.mingw64.txt等文件,把国内镜像源加在文件最前面,比如清华 TUNA 的地址:

Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/ucrt64/

然后重新打开 MSYS2 终端执行pacman -Syu,速度会有质的提升。这里提醒一句,修改这些文本文件时推荐用 VS Code 或 Notepad++,不要用系统自带的记事本,因为可能出现编码或换行符问题,别问我是怎么知道的。

提示:MSYS2 安装完成后,开始菜单里会出现多个快捷方式,比如“MSYS2 MSYS”“MSYS2 UCRT64”“MSYS2 MINGW64”“MSYS2 CLANG64”。日常 C++ 开发请认准“MSYS2 UCRT64”,不要一上来就双击 MSYS2 MSYS,用错环境后面编译出来的程序会有运行时依赖问题。

2.2 选择正确的环境:为什么推荐 UCRT64 而不是 MINGW64

很多初学者第一次打开 MSYS2,看到一堆入口就懵了。这几个环境有什么区别?我用最简单的话说清楚:

  • MSYS 环境:自带的工具链生成的程序依赖 msys-2.0.dll 运行库,严格来说不能算“原生 Windows 程序”,主要用于运行 shell 脚本、GNU 工具,不适合做最终产品。
  • MINGW64 环境:使用 msvcrt 作为 C 运行时,兼容老式 Windows 程序,但 msvcrt 过于古老,C 标准支持不完整,新代码有潜在兼容问题。
  • UCRT64 环境:使用 Universal C Runtime,Windows 10 及以后系统原生支持,C 标准支持完善,适合现代 C++ 开发。
  • CLANG64 环境:以 Clang 为默认编译器,适合你明确想用 Clang 而不是 GCC 的情况。

所以结论很清楚:现代 Windows 10/11 用户直接用 UCRT64 是最稳的选择。如果你要开发的项目要求兼容很老版本的 Windows,那再考虑 MINGW64;如果你对工具链有洁癖并且钟爱 Clang,那就选 CLANG64。本文后面所有命令都以 UCRT64 环境为准。

另外提一句,如果你在普通 CMD 或 PowerShell 里看到“MSYS2”这些入口,本质上都是通过msys2_shell.cmd脚本启动的,它们共享同一个安装目录,只是环境变量和默认工具链的 PATH 前缀不同。所以切换环境其实切换的是PATH里把哪个 bin 目录放在最前面。

2.3 用 pacman 安装 GCC、GDB、CMake、Ninja 全家桶

打开“MSYS2 UCRT64”终端,先做一次全量更新:

pacman -Syu

这里有个小提示:如果更新过程中提示某个包被占用或者终端自动关闭,通常是因为 bash 进程或 pacman 自身还在运行。解决办法是关闭所有 MSYS2 相关窗口,重新打开终端后再执行一次。接着安装核心工具链:

pacman -S --needed \ mingw-w64-ucrt-x86_64-gcc \ mingw-w64-ucrt-x86_64-gdb \ mingw-w64-ucrt-x86_64-cmake \ mingw-w64-ucrt-x86_64-ninja \ mingw-w64-ucrt-x86_64-make

很多教程这里会漏掉--needed,它的作用是跳过已经安装的包,避免重复安装浪费时间。注意看包名前缀:mingw-w64-ucrt-x86_64-表明这是 UCRT64 环境用的包,如果你的包名不带这个前缀,那多半装到了 MSYS 环境里,最后编译出的程序依赖 MSYS 运行时,分发时会非常被动。这也是新手最容易踩的坑之一。

安装完成后,在同一个终端里验证一下:

which gcc gcc --version which cmake cmake --version

如果都能正常输出版本信息,那就说明工具链装好了。这里不推荐单独去官网下载 MinGW-w64 安装器或 TDM-GCC,因为它们没有包管理机制,后续升级和装库都麻烦,体验远不如 MSYS2 的 pacman 来得舒服。

2.4 把 UCRT64 的 bin 目录加入系统 PATH

工具链在 MSYS2 终端里能用了,但如果你希望在 VSCode 的集成终端或者普通 PowerShell 里直接敲gcccmake,就得把C:\msys64\ucrt64\bin加入系统 PATH。

具体操作:Win + I 打开设置 → 系统 → 关于 → 高级系统设置 → 环境变量,在“系统变量”或“用户变量”里找到 Path,点编辑,新建一行,填入C:\msys64\ucrt64\bin,保存后重新打开一个终端窗口验证。

这里有一个非常重要的避坑点:只加ucrt64\bin,不要把C:\msys64\usr\bin加进去。因为usr\bin里包含很多 GNU 工具,比如 find、sort、where 等,系统里同样有同名 Windows 命令,一旦usr\bin排在系统目录前面,很多系统自带命令会被覆盖,日常使用会出现各种莫名其妙的兼容问题。我第一次配的时候图省事直接全加进去了,结果 PowerShell 里的where命令行为都变了,排查了好久才发现是 PATH 冲突。

加好 PATH 之后,在 PowerShell 里试试:

gcc --version cmake --version ninja --version

这里要记住:终端窗口必须重新打开,老窗口里 PATH 可能还是旧值。如果你加了 PATH 却仍然找不到命令,大概率是你没重开终端,或者 PATH 编辑错了层级。

3. GCC 版本与软件包管理的深度避坑

3.1 已经升级了 GCC,为什么gcc --version还是旧版本?

这个问题绝对是 MSYS2 相关搜索里的高频问题,我自己也遇到过。场景是这样的:你执行pacman -Syu升级了 GCC,终端里明明显示安装的是 13.x,可一运行gcc --version,屏幕上还是 9.x 或 10.x。

原因很可能是你的系统里同时装了多个 GCC,比如 Git 自带的 MinGW、Qt 自带的编译器、Rtools 工具链、Anaconda 自带的编译器,或者你之前手动安装过 TDM-GCC。Windows 在解析命令时按 PATH 顺序从前到后找,找到第一个 gcc.exe 就用了,根本不会管它版本老不老。

排查方法很简单,在命令行里输入:

where gcc

或者用 PowerShell:

Get-Command gcc | Format-List Source

看到输出后,基本就能确认你实际调用的 gcc 到底在哪个目录。解决办法有几个:

  • C:\msys64\ucrt64\bin移动到 PATH 列表的最前面。
  • 卸载多余的工具链,比如把 Qt 自带的 MinGW 目录从 PATH 里移除。
  • 在项目中使用 CMake 时,通过参数显式指定编译器,不给它挑错的机会。

另外还有一个隐蔽原因:如果你在升级前就开着终端,升级后终端进程里保存的路径缓存可能还是旧路径。重新打开一个全新终端通常就能看到新版本。所以下次升级完 GCC,第一件事是 pwd 一下确认自己在哪,然后新开窗口执行版本检查。

3.2 用 pacman 查询和管理包,别让自己迷失在二进制里

使用 MSYS2 一段时间后,你会发现你装了非常多的包,如果不借助包管理器记录,你根本搞不清某个 gcc.exe 是从哪来的。好在 MSYS2 的 pacman 提供了跟 Arch Linux 一样的能力:

# 查看当前 gcc 属于哪个软件包 pacman -Qo $(which gcc) # 查询所有已安装包里与 gcc 相关的 pacman -Qs gcc # 查看某个包详细信息 pacman -Si mingw-w64-ucrt-x86_64-gcc

这几个命令其实很值得养成习惯。因为一旦系统里存在多个 GCC,用pacman -Qo能立刻知道你现在用的这个 gcc 是否真的属于 MSYS2 的包,避免在错误工具链上浪费时间。

顺带说一句,之前我看到网上有人问“Linux 离线安装 GCC 怎么这么麻烦”或“CentOS 8 离线下载 GCC 依赖包很难”,如果你只是需要在 Windows 上开发 C++,那 MSYS2 的 pacman 会让你体会到什么叫一条命令解决所有依赖。虽然 Linux 和 Windows 系统不同,但这种包管理的思路是很一致的。

提示:尽量不要手动从网上下载 exe 安装包来给 MSYS2 补工具。MSYS2 自己的仓库覆盖了绝大多数场景,手动安装的东西往往不在数据库里,后续升级和卸载都会很麻烦,还可能引发版本冲突。

3.3 如何在同一台机器上共存多个编译器版本

有时候项目要求老版本 GCC,比如某些嵌入式交叉编译器只支持 GCC 8,但 MSYS2 仓库里默认的应该是比较新的 GCC。这时候怎么办?

MSYS2 官方仓库一般只提供一个最新版 GCC 包,但你可以用pacman -U直接安装本地下载的旧版包文件。你可以从 MSYS2 软件包仓库页面找到历史版本,下载对应的.pkg.tar.zst文件,然后:

pacman -U mingw-w64-ucrt-x86_64-gcc-8.3.0-1-any.pkg.tar.zst

注意,pacman -U在安装时不会自动处理依赖关系,如果旧版需要特定依赖,你要先手动装好。这个操作相对专业,普通项目没必要折腾,仅作为备选方案。

更常见的情况是你想用 Clang 替代 GCC 编译某个项目。MSYS2 里可以同时装 Clang 和 GCC,它们互不冲突:

pacman -S --needed mingw-w64-ucrt-x86_64-clang

然后在 CMake 构建时指定编译器:

cmake -S . -B build -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++

这对喜欢在 Linux 上用 Clang、在 Windows 上也想保持一致体验的人来说,是很顺手的操作。而且因为 GCC 和 Clang 都遵循相同的命令约定,切换成本非常低,这也是不要在 Windows 上死守 MSVC 工具链的一个原因。

4. CMake 配置与项目构建实战

4.1 为什么 CMake 和 Ninja 要从 pacman 安装,而不是官网下载

很多人会习惯性地去 CMake 官网下载 Windows 安装包,但在这套方案里我强烈建议直接用 MSYS2 仓库里的mingw-w64-ucrt-x86_64-cmakemingw-w64-ucrt-x86_64-ninja。原因是官网版 CMake 不一定能自动识别 MSYS2 环境里的编译器位置;而通过 pacman 安装的 CMake,安装时已经把 MSYS2 的文件路径和默认工具链信息放进配置里,你在 UCRT64 终端里执行 cmake 时,它能直接定位到 gcc、g++、ar、ranlib 这些工具,不需要手动指定一堆变量。

Ninja 也是同理。Ninja 是一个极简高效的构建工具,它本身只负责按描述文件执行命令,不会帮你推测编译器,所以配合 CMake 使用恰到好处。比 Makefile 的构建速度快,输出信息也更干净,尤其适合在 CI 环境和本地频繁增量编译的场景。

完成安装后,可以在 UCRT64 终端里验证:

cmake --version ninja --version

如果这两个命令能正常输出,说明环境已经就绪。

4.2 第一个最小 CMake 项目:从源码到可执行文件

为了让你尽快走上正轨,我写一个最小的 C++ 项目。先创建目录:

mkdir -p ~/hello && cd ~/hello

~/hello下创建src/main.cpp,内容就用经典的冒泡排序练手,顺便验证 C++17 特性:

#include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> vec = {5, 2, 9, 1, 7, 6}; // 冒泡排序 for (size_t i = 0; i < vec.size(); ++i) { for (size_t j = 0; j < vec.size() - 1 - i; ++j) { if (vec[j] > vec[j + 1]) { std::swap(vec[j], vec[j + 1]); } } } for (int v : vec) { std::cout << v << ' '; } std::cout << '\n'; return 0; }

再创建CMakeLists.txt

cmake_minimum_required(VERSION 3.15) project(hello CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_executable(hello src/main.cpp)

然后在项目根目录执行:

cmake -S . -B build -G Ninja cmake --build build ./build/hello.exe

简单解释一下命令的含义。-S .告诉 CMake 源码目录在哪;-B build指定构建目录为 build,所有中间文件都会放在这里,不会污染源码目录;-G Ninja指定生成器。为什么不直接执行make?因为在 UCRT64 里 Ninja 通常并行度更高,构建更干净,而且不用处理 Makefile 里常见的 Windows 路径转义问题。

如果一切顺利,你会看到终端输出排好序的1 2 5 6 7 9。到这里你就已经拥有了一个可复制的 CMake + GCC 完整流程。

注意:如果你执行cmake --build build后找不到生成的 exe,先检查一下 build 目录里的确有hello.exe;如果运行 exe 提示缺少 DLL,多半是 PATH 没配好,回到 2.4 节把ucrt64\bin加入系统 PATH 即可。

4.3 VSCode 配合使用:tasks.json、launch.json 和 c_cpp_properties.json

MSYS2 终端虽好用,但写代码还是需要编辑器。我日常主力是 VSCode,搭配 C/C++ 官方插件、CMake Tools 插件和 CMake 插件,体验接近轻量 IDE。不过这里有个大坑:VSCode 的 C/C++ 插件默认会尝试使用 MSVC,如果你想用 GCC,需要自己在配置里显式指定。

首先是.vscode/c_cpp_properties.json,告诉 IntelliSense 使用我们的 GCC:

{ "configurations": [ { "name": "MSYS2-UCRT64", "intelliSenseMode": "windows-gcc-x64", "compilerPath": "C:/msys64/ucrt64/bin/g++.exe", "includePath": [ "${workspaceFolder}/**", "C:/msys64/ucrt64/include/**" ], "cStandard": "c17", "cppStandard": "c++17" } ], "version": 4 }

接着是.vscode/tasks.json,让 Ctrl+Shift+B 可以直接构建:

{ "version": "2.0.0", "tasks": [ { "label": "cmake-build", "type": "shell", "command": "cmake --build build", "group": { "kind": "build", "isDefault": true }, "options": { "cwd": "${workspaceFolder}" } } ] }

最后是.vscode/launch.json,配置 GDB 调试:

{ "version": "0.2.0", "configurations": [ { "name": "C++ GDB", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/build/hello.exe", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": false, "MIMode": "gdb", "miDebuggerPath": "C:/msys64/ucrt64/bin/gdb.exe", "setupCommands": [ { "description": "Enable pretty-printing", "text": "-enable-pretty-printing", "ignoreFailures": true } ] } ] }

这里最容易踩的坑就是miDebuggerPath。很多人第一次调试时报“找不到 miDebuggerPath”,其实就是忘了安装mingw-w64-ucrt-x86_64-gdb,或者路径写成了C:/msys64/mingw64/bin/gdb.exe。注意我们用的是 UCRT64 环境,所以 gdb 在ucrt64\bin下,别写错。

另外,如果你使用 CMake Tools 插件,记得在设置里指定:

{ "cmake.generator": "Ninja", "cmake.cmakePath": "C:/msys64/ucrt64/bin/cmake.exe", "cmake.configureSettings": { "CMAKE_C_COMPILER": "C:/msys64/ucrt64/bin/gcc.exe", "CMAKE_CXX_COMPILER": "C:/msys64/ucrt64/bin/g++.exe" } }

这样在 CMake Tools 里点一下,就能完成配置、构建和运行,不用手动敲命令。

4.4 引入第三方库:像 Linux 一样“一条命令装好”

这套方案的优势在安装第三方库时才真正体现出来。比如说我想在项目里用 fmt 库,在 Linux 上可能是apt install libfmt-dev,在 MSYS2 里就是:

pacman -S --needed mingw-w64-ucrt-x86_64-fmt

然后在CMakeLists.txt里:

find_package(fmt CONFIG REQUIRED) target_link_libraries(hello PRIVATE fmt::fmt)

就是这么简单。MSYS2 仓库里大量主流 C++ 库都有现成的预编译包,例如 OpenCV、Boost、SQLite3、cURL、yaml-cpp 等。你不需要像 vcpkg 那样把源码编译一遍,也不用担心 ABI 不匹配,因为这些库都是和同一个 GCC 版本一起构建的,兼容性经过了仓库维护者的保障。

对比一下 vcpkg 的体验:vcpkg 默认用 MSVC 工具链,很多时候你需要等库源码编译完成,耗时非常长;而且如果项目里某个库只有 Linux 生态,你可能还得查半天文档才能编译过。MSYS2 在这个场景下真的更接近“装即用”的体验。

5. 常见问题实录与排查技巧

5.1 安装和更新过程中的奇葩问题:锁文件、签名、镜像

不管你是第一次装 MSYS2,还是已经用了一段时间,pacman 相关的问题大概率会遇到几个。我把最常出现的几类列出来:

  • pacman 卡住不动。多半是在下载软件包时镜像源慢或连接不稳定。解决办法是先关掉当前窗口,编辑对应 mirrorlist 文件,把国内镜像地址放在最前,再重新执行更新。
  • 报错:could not lock database。说明 pacman 的数据库被锁住了,多半是之前有 pacman 进程没有正常退出。删除锁文件即可:
rm /var/lib/pacman/db.lck
  • 更新时提示签名无效或 key 错误。这通常是因为 MSYS2 的 GPG 密钥过期或者本地 keyring 太旧,执行:
pacman -Sy msys2-keyring
  • 安装后终端一闪而过。不要慌,多半是入口脚本有问题,检查一下是否有杀毒软件拦截了 msys2_shell.cmd,或者直接右键以管理员身份运行。

这些问题的本质其实跟 Arch Linux 用户遇到的很像。只要记住 pacman 是一个精确的数据库管理工具,不要随便强杀进程,很多问题都能避免。

5.2 Python 环境里报“Microsoft Visual C++ 14.0 or greater is required”怎么破

这个报错大概是最多人搜索的 C++ 环境报错之一,但并不是你装了 MSYS2 就能彻底绕开,要分情况看。这个错误通常发生在你用pip install安装某些需要 C++ 编译的 Python 包时,比如 dlib、opencv-python 源码编译等。pip 在 Windows 上只会默认找 MSVC 编译器,找不到就报“Microsoft Visual C++ 14.0 or greater is required”。

如果你确定自己在 MSYS2 的 GCC 环境里,这个报错依然出现,说明当前 Python 的构建配置没有指向 GCC。几个可行的解决思路:

  • 优先选择预编译的 wheel 包,比如在官方 PyPI 上下载.whl文件,或者用conda install去装,conda 好就好在自带编译好的二进制。
  • 实在需要源码编译,可以试着在 PowerShell 里设置环境变量CC=gccCXX=g++,并确保 PATH 里能搜到 MSYS2 的 gcc。但要注意,Python 的 setuptools 在 Windows 下对非 MSVC 编译器的支持并不算非常好,可能需要折腾。
  • 如果只是单纯想把某个包编译出来,也可以试试 MSYS2 仓库里已有的包。比如dlib,直接:
pacman -S --needed mingw-w64-ucrt-x86_64-dlib

这样就不用走 Python 源码编译这条路了。

再说个容易被混淆的点:很多人看到“MSVC 14.0 or greater”就以为要装 VC++ Redistributable。这完全是两回事。报错缺的是编译器,不是运行库。如果你只是运行某个程序提示缺少 vcruntime140.dll,那才需要下载安装 VC++ Redistributable。把这个区别弄清楚,可以少走很多弯路。

5.3 编译好的 exe 在别人电脑上运行提示缺少 DLL

用 GCC 编译的程序在 Windows 上运行时,默认会依赖一些 GCC 运行库 DLL,最常见的是libstdc++-6.dlllibgcc_s_seh-1.dlllibwinpthread-1.dll。如果目标电脑上没装 MSYS2,也没有把ucrt64\bin加入 PATH,那么双击 exe 就会弹窗提示找不到某个 DLL。

解决办法有两种。最简单粗暴的,是把C:\msys64\ucrt64\bin加入系统 PATH,这对你自己开发足够用了。但如果你要把程序发给别人,总不能要求别人也装一个 MSYS2,这时就需要把依赖 DLL 也一起分发。可以用 MSYS2 自带的工具查看依赖:

ntldd hello.exe

它会列出所有依赖的 DLL 路径,你直接把里面位于C:\msys64\ucrt64\bin下的那几个 DLL 复制到 exe 同目录下,再打包发给对方。大部分场景下就是上面那三个 DLL,不过为了保险,还是以ntldd的输出为准。

另外再提醒一下:不要强行把这些 DLL 静态编译进 exe。GCC 的工具链默认是动态链接标准库,你确实可以用-static-libgcc -static-libstdc++静态链接,但并不是所有场景都推荐,静态链接可能导致程序体积变大,某些平台兼容性反而会变差。

5.4 CMake 找不到编译器、或者默认选了 Visual Studio 生成器

这个问题也特别常见。如果你在 Windows 上装了 VS,又装了 MSYS2,那么执行cmake -S . -B build时,CMake 很可能默认使用“Visual Studio 17 2022”生成器,而不是 Ninja 或 Makefiles。你明明想让 CMake 用 GCC,结果它跑去用 MSVC,自然就出现“编译器识别失败”或根本找不到 gcc 的情况。

解决办法很直接:统一在 MSYS2 UCRT64 终端里执行 CMake,并且显式指定生成器和编译器

cmake -S . -B build -G Ninja \ -DCMAKE_C_COMPILER=C:/msys64/ucrt64/bin/gcc.exe \ -DCMAKE_CXX_COMPILER=C:/msys64/ucrt64/bin/g++.exe

如果不写-G Ninja,CMake 在 Windows 上可能会优先挑 VS 生成器,哪怕你的 PATH 里已经有 gcc。这个行为虽然可以通过 CMake 设置调整,但最稳妥的方法就是每次都显式告诉它你要用什么工具链。

还有一个老生常谈的问题:修改了CMakeLists.txt里的编译器选项,重新 cmake 之后却没生效。这是因为 CMake 有缓存机制,它不会自动检测编译器变更。解决方式是把 build 目录直接删掉,重新从零配置:

rm -rf build cmake -S . -B build -G Ninja

记住这条万能规则:当 CMake 的行为不符合预期时,先删 build 目录再重试,能解决 90% 的缓存问题。

5.5 顺带说说 Linux 环境里的 GCC 安装问题

搜这个词条的人很多,可能是大家在 Windows 上配好环境后,又跑到 Linux 服务器上部署项目时遇到了麻烦。其实在大多数主流 Linux 发行版上,GCC 安装就是一行命令:

# Ubuntu/Debian sudo apt install build-essential # CentOS/RHEL/Fedora sudo yum groupinstall "Development Tools"

难的是离线环境。“CentOS 8 gcc 依赖包离线下载”这种场景,通常是内网服务器没有外网访问权限。办法是把依赖 RPM 包在能联网的机器上下载好,再拷贝进内网安装:

yumdownloader --resolve gcc gcc-c++ make

下载后拷贝到目标机器,执行:

rpm -ivh *.rpm

Ubuntu/Debian 对应的则是用apt download,或者用dpkg -i *.deb。说实话这些步骤多少有些繁琐,所以我才觉得 MSYS2 这套在 Windows 上能一条命令装完所有工具链的方案真的很舒服,至少在你还没深入 Linux 运维之前,它能让你专心写代码,把精力放在项目本身。

6. 从 Windows 走向 Linux 式工作流:更多实用技巧

6.1 像 Linux 的 apt 一样管理 C++ 常用库

用 MSYS2 一段时间之后,你会越来越体会到它跟“Linux 发行版 + 包管理器”真的很像。这里我把常用命令整理一下,方便你随手翻:

目的命令
搜索某个库pacman -Ss opencv
查看某个包详情pacman -Si mingw-w64-ucrt-x86_64-opencv
安装某个库pacman -S --needed mingw-w64-ucrt-x86_64-opencv
查看已安装包pacman -Q
查看某个文件属于哪个包pacman -Qo /path/to/file
卸载某个库pacman -R mingw-w64-ucrt-x86_64-opencv

这些命令会极大提升你管理开发环境的效率。我个人经验是,先把常用的库都通过 pacman 装好,比如 Boost、fmt、spdlog、yaml-cpp、OpenCV、cURL、SQLite3,以后写项目基本不用再为安装依赖发愁。

有一点要注意:别在 MSYS 环境里给 UCRT64 的包做各种混装。不同子环境的包是不通用的,ucrt64的包只能用mingw-w64-ucrt-x86_64-前缀,mingw64的包对应mingw-w64-x86_64-前缀。如果你用错前缀,CMake 可能会在包含目录和链接库目录上产生混乱。

6.2 不启动 IDE,也能高效进行日常 C++ 练习

对于刷题或者快速验证代码片段,完全不需要打开 VSCode 或 VS,直接在 MSYS2 UCRT64 终端里用命令行就够了。比如写一个二分查找的练习:

#include <iostream> #include <vector> int binarySearch(const std::vector<int>& arr, int target) { int left = 0; int right = static_cast<int>(arr.size()) - 1; while (left <= right) { int mid = left + (right - left) / 2; if (arr[mid] == target) return mid; if (arr[mid] < target) left = mid + 1; else right = mid - 1; } return -1; } int main() { std::vector<int> arr = {1, 3, 5, 7, 9}; std::cout << binarySearch(arr, 5) << '\n'; return 0; }

编译运行一条龙:

g++ -std=c++17 -Wall -Wextra -O2 binary_search.cpp -o bs && ./bs

这里-Wall -Wextra打开常见警告,写练习代码时尽早发现隐患,-O2开启优化。如果你习惯在 bash 里设置别名,还可以在~/.bashrc里加一行:

alias g++17='g++ -std=c++17 -Wall -Wextra -O2'

之后每次编译就只敲g++17 xxx.cpp -o xxx,省时省力。

提示:MSYS2 的用户目录映射为C:\msys64\home\你的用户名\,所以编辑~/.bashrc实际上就是在编辑这个路径下的文件。Windows 侧访问时不要找错地方。

6.3 用 CMake 统一构建,让代码“一处编写,处处编译”

如果你将来会把同一个 C++ 项目部署到 Linux 服务器上,那么从第一天起就坚持用 CMake + 某个生成器这套方案,会给你省下大量重写构建脚本的时间。VS 的 .sln 文件跨平台完全用不了,但 CMakeLists.txt 在哪个平台几乎都通用。

具体做法很简单:

  • 项目里不引入 Windows 专用 API,如果有平台差异,用#ifdef _WIN32包起来。
  • 所有依赖库通过 MSYS2 包管理器安装,并把 find_package 写在 CMakeLists.txt 里。
  • 本地开发用 Ninja,到了 Linux 上用同一份 CMakeLists.txt 切换成 Unix Makefiles:
cmake -S . -B build -G "Unix Makefiles" cmake --build build

基本不需要改任何代码和构建配置。这背后的逻辑就是:CMake 是一层抽象,它把“编译什么、链接什么”和“用什么工具链编译”解耦了,所以只要坚持用 CMake,哪怕以后换到 macOS 也不会有太大障碍。

如果你需要在 Windows 上跑 Linux 专属的服务器程序,可以配合 WSL 使用,WSL 和 MSYS2 并不冲突:MSYS2 负责生成原生 Windows 程序,WSL 提供真正的 Linux 环境,两边可以同时存在,互相补位。

6.4 给初学者的几条终极避坑清单

最后结合我之前踩过的坑,给你列几条非常实用的检查清单:

  • 安装路径不要有中文、空格,这是 Windows 下 C/C++ 工具链最容易出现的隐性坑。
  • 不要把所有 MSYS2 的目录都加入系统 PATH,只需要加ucrt64\bin,否则命令冲突会搞得你怀疑人生。
  • 在 MSYS2 UCRT64 终端里执行命令,而不是 CMD 或 PowerShell 里乱试,除非你确认 PATH 已经配置好。
  • 编译报错时先看报错信息的前几行,不要盯着“error:”后面的红色字发呆,往往前面那几行才是真正的提示。
  • CMake 缓存问题导致行为异常时,直接删 build 目录重来,这比反复改 cmake 参数管用得多。
  • 任何 exe 运行找不到 DLL 时,第一时间检查 PATH 是否包含C:\msys64\ucrt64\bin,大概率就是这个问题。

这篇东西写到现在,核心内容基本都在上面了。我个人在实际操作中的体会是,MSYS2 + GCC + CMake 这套环境最重要的不是某一个工具,而是一整套“包管理 + 命令行 + 构建系统”的思维模式。当你习惯用 pacman 装库、用 CMake 组织项目、用 Ninja 加速构建之后,再回到 VS 里点鼠标配置属性页,会觉得格外低效。

最后再分享一个小技巧:如果你经常要在不同项目里切换 GCC 和 Clang,可以在项目目录下放一个toolchain-ucrt.cmake工具链文件,把编译器路径、标准库路径都写进去,然后在 CMake 里用-DCMAKE_TOOLCHAIN_FILE=...指定,这样项目可复现性会好很多。搭配CMakePresets.json使用更佳,不过那是另一个话题了,有机会再细聊。希望你在 Windows 上也能早日实现“编译自由”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询