Windows 下 MinGW-w64 GCC 13.1.0 离线包配置与避坑
简介:这份离线安装包面向需要在64位Windows上搭建原生C/C++开发环境的程序员与学习者,核心是GCC 13.1.0正式稳定版,采用posix线程模型、SEH结构化异常处理与ucrt统一C运行时库,无需联网即可一次性完成部署,适合网络受限或希望固定工具链版本的项目场景。压缩包为7z格式,约68.89MB,内含18725个文件,以html文档、h头文件、a静态库、hpp模板头、py脚本与少量exe可执行程序为主,另有tcl终端配置、dll动态库、txt说明等,解压后呈现bin、lib、include等标准目录,便于按需检索。目前已有1649人学习下载。读者可直接获得gcc、g++编译器、链接器、标准库与头文件的完整组合,覆盖C与C++的编译、链接与调试链路,并支持对C++20/23新特性的尝鲜验证;固定版本的工具链也有助于复现旧项目构建结果、排查编译差异,是Windows下跨平台开发与原生64位应用构建的实用基础环境。
1. 一个版本字符串引发的翻车:为什么我坚持用 x86-64-13.1.0-release-posix-seh-ucrt-rt-v11-r 离线包
上周帮一个同事收拾构建环境,项目 clone 下来,cmake一跑就报找不到编译器,他翻出电脑里躺了两年的 MinGW,解压、加环境变量,gcc -v倒是能出版本号,结果一编译就死在std::filesystem上,又换了个包,链接阶段报_Unwind_Resume未定义。折腾一下午,最后换成本文要讲的这个x86-64-13.1.0-release-posix-seh-ucrt-rt-v11-r离线包,五分钟跑通。这套环境在 Windows 上做 C/C++ 开发太常见了,常见到很多人以为「解压即用」,可版本线程模型选错、运行库选错,就会出现编译能过链接炸、链接能过运行炸的玄学现象。这篇笔记写给正在 Windows 上配 GCC 工具链的人:不管你是第一次装 MinGW-w64,还是被旧环境坑过想重装一套干净的,照着走一遍就行。
2. 拆解安装包:x86-64、posix、seh、ucrt 各自管什么
动手之前先把名字读懂,这一串不是随机生成的,每一位都对应一个编译期或运行期的决定,选错了后面 all 是白费功夫。离线包的好处是不依赖网络,解压即得,适合内网机器、老笔记本和不想被下载中断打断的场景。
2.1 版本号与目标架构:x86-64 与 13.1.0 的取舍
x86-64指目标架构是 64 位 Windows,产出的是 PE32+ 格式的可执行文件。如果你的项目还要编 32 位程序,那是另一套i686包的事,别指望一个包通吃。13.1.0是 GCC 的主版本号,这个版本对 C++20 的 ranges、concepts 支持已经相当完整,C++23 的部分特性也有了实验性支持,比 8.x、9.x 那批老包在标准库实现上干净很多。
我一般不建议追最新的大版本,也不建议用太老的。新版本刚出时生态适配没跟上,某些第三方库的头文件会和新标准库打架;太老的版本又会卡在std::format、<bit>这类头文件缺失上。13.1.0 属于比较稳的中间点。
安装前建议先确认本机情况,用 PowerShell 跑一下:
# 确认系统架构与已存在的编译器 [Environment]::Is64BitOperatingSystem where.exe gcc where.exe g++ where.exe mingw32-make第一条返回True才能用 x86-64 包。后三条如果输出了路径,说明系统里已经有别的 MinGW 或 MSYS2、Cygwin 提供的 gcc,这是后面很多怪问题的源头——多个工具链抢 PATH,一定要先记下这些路径,装完新包后把它们从 PATH 里清掉,或者干脆调整优先级。
2.2 线程模型 posix 与异常模型 seh:两个最容易选错的参数
posix是线程模型。它决定了 libstdc++ 里std::thread、std::mutex、std::condition_variable这些设施底层接的是哪套线程实现。Windows 上还有另一种叫win32的线程模型,好处是不依赖额外的运行时 DLL,坏处是对 C++11 线程库的支持残缺,很多依赖std::thread的代码在 win32 模型下直接编译失败或行为异常。只要你的项目用了标准库线程,就选posix,没有例外。
seh是异常模型,指结构化异常处理,是 64 位下唯一正确的选择。老教程里常见的sjlj是给 32 位准备的,在 64 位下用 sjlj 会带来性能损失,而且和某些库的异常展开不兼容。dwarf更是 32 位专属,出现在 64 位包名里基本都是错的。这三个参数在包名里是连写的,看的时候别漏:
| 参数位 | 本包取值 | 备选 | 选错的后果 |
|---|---|---|---|
| 架构 | x86-64 | i686 | 与目标平台不符,无法链接 64 位库 |
| 线程模型 | posix | win32 | std::thread 编译失败或运行异常 |
| 异常模型 | seh | sjlj / dwarf | 异常展开失效、性能下降、链接报错 |
| 运行库 | ucrt | msvcrt | 与新版 Windows SDK 冲突,CRT 符号重复定义 |
2.3 运行库 ucrt 与包版本 v11:和系统 DLL 的对应关系
ucrt指 Universal C Runtime,是 Windows 10 之后系统自带的 C 运行库,Win7 需要单独装更新。用 ucrt 编译出来的程序,运行时依赖ucrtbase.dll,在 Win10/11 上是系统组件,分发时不用额外带。而msvcrt是更老的 VC++ 6.0 时代那套,功能残缺,和现代 C++ 标准库配合容易出问题。现在新包基本都以 ucrt 为主,除非你要兼容非常老的系统,否则不要选 msvcrt。
包名末尾的rt-v11是运行时包的版本标记,rt表示 runtime,v11是这一系列运行时构建的编号。它跟 GCC 13.1.0 是配套的,说明这个离线包里的运行时组件是和第 11 代工具链一起构建出来的。这一点在实际使用中的意义是:不要把这个包里的 DLL 和别的版本的 MinGW 混用,混用会出现「编译期头文件是新的、运行期 DLL 是旧的」这种最难查的黑匣子问题。
3. 离线包落地:解压路径、环境变量与配置文件
离线安装包的核心优势就是不需要包管理器、不需要联网、不需要管理员权限(放用户目录即可)。但「解压」这两个字背后有几个必须做对的细节,做错了就会出现命令找不到、路径带空格、多版本互相覆盖。
3.1 解压位置与目录结构确认
先建一个固定的工具链根目录,我习惯用D:\dev\mingw64,不要放在C:\Program Files下面,那个路径带空格,某些老旧的 Makefile 和脚本处理不了;也不要放在桌面或下载目录,路径里有中文和空格的概率太高。
解压后检查目录结构,应该能看到这些关键子目录:
# 解压后进入根目录,确认关键文件存在 ls bin/gcc.exe bin/g++.exe bin/mingw32-make.exe ls x86_64-w64-mingw32/lib/libstdc++.a ls include/c++/13.1.0/vectorbin下是驱动程序和工具,x86_64-w64-mingw32是目标三元组目录,里面放库和头文件,include/c++/13.1.0是标准库头文件路径。这三处任何一处缺失,说明包下载不完整,重新解压一遍,别硬撑着往下走。有些打包方式会多套一层同名目录,解压完先看清层级,保证bin直接位于你设定的根目录下。
3.2 PATH 配置:优先级比路径本身更重要
环境变量配置这一步是血泪经验高发区。正确做法是把D:\dev\mingw64\bin加到用户 PATH 的最前面,不是追加到末尾。追加到末尾的话,系统里原有的其他 gcc、conda 自带的编译器、某些 IDE 捆绑的工具链会抢先命中,你就得到一个「明明装了新版却报旧版本号」的诡异现场。
图形界面操作路径是:系统属性 → 环境变量 → 用户变量里的 Path → 编辑 → 新建一行填入D:\dev\mingw64\bin→ 用右侧「上移」按钮把它顶到第一位。命令行方式(当前用户,无需管理员):
# 将 MinGW bin 目录置于用户 PATH 首位 $mingwBin = 'D:\dev\mingw64\bin' $oldPath = [Environment]::GetEnvironmentVariable('Path', 'User') if ($oldPath -notlike "*$mingwBin*") { [Environment]::SetEnvironmentVariable('Path', "$mingwBin;$oldPath", 'User') }这段脚本先读当前用户的 Path,判断目标路径是否已存在,避免重复添加把 PATH 撑得越来越长(PATH 过长会导致部分程序启动失败,这是个隐蔽的坑)。写入的是用户级变量,不需要管理员权限,也不影响系统其他账号。
改完后必须新开一个终端窗口,旧窗口的环境变量是启动时快照的,不会自动刷新——这一条每年都能坑到一批人。
3.3 验证编译器身份:别让版本号骗了你
配置完第一件事不是急着编译项目,而是确认当前命中的到底是哪个编译器:
gcc -v g++ -v where gccgcc -v的输出里重点看三行:Target: x86_64-w64-mingw32、Thread model: posix、gcc version 13.1.0。这三项和包名对得上,才说明你用的是这个包。where gcc用来确认路径只有一条,如果输出了多行,回到 3.2 节清理 PATH。
如果gcc -v报「无法将 gcc 项识别为 cmdlet」,说明 PATH 没生效,先检查路径拼写、是否新开了窗口;如果报的是版本号不对,那就是优先级问题,用where gcc定位是谁在抢。
4. 配置与编译链路:让 gcc、g++、make 真正可用
版本确认无误后,接下来要把这套工具链接进实际的项目构建流程。很多人卡在这一步不是因为工具链坏了,而是因为 CMake、Makefile、IDE 里还残留着旧路径,或者编译参数没跟着运行库走。
4.1 编译参数与运行库一致性
用 ucrt 工具链时,编译和链接要保证运行库口径一致。写个最小用例先跑通:
# test_ucrt.cpp:同时验证线程库与文件系统 #include <iostream> #include <thread> #include <filesystem> int main() { std::cout << "thread id: " << std::this_thread::get_id() << "\n"; std::cout << "cwd: " << std::filesystem::current_path() << "\n"; return 0; }编译命令:
g++ -std=c++20 -O2 test_ucrt.cpp -o test_ucrt.exe -pthread-std=c++20打开 C++20 标准,13.1.0 对这个标准的支持已经比较完整;-O2是常规优化级别,调试期可以换成-g -O0;-pthread在 posix 线程模型下用于开启线程支持,虽然 MinGW 上不加有时也能过,但涉及std::thread时加上更稳妥。运行test_ucrt.exe,能同时打印线程 ID 和当前目录,说明线程库、文件系统库、C 运行库三者都是通的。
如果这里报filesystem相关链接错误,基本可以判定用的是老版本 GCC 或者 msvcrt 口径的包;如果报线程相关的未定义符号,那就是线程模型选成了 win32。
4.2 CMake 与 Makefile 中的工具链指定
命令行能编不代表项目能编,CMake 项目最容易在工具链探测上翻车。稳妥的做法是显式指定编译器,别让它自己猜:
cmake -S . -B build -G "MinGW Makefiles" ^ -DCMAKE_C_COMPILER=D:/dev/mingw64/bin/gcc.exe ^ -DCMAKE_CXX_COMPILER=D:/dev/mingw64/bin/g++.exe ^ -DCMAKE_BUILD_TYPE=Release cmake --build build -j 8-G "MinGW Makefiles"明确生成器,避免 CMake 抓成 Visual Studio 的生成器;两个CMAKE_*_COMPILER用绝对路径写死,防止它从注册表或 PATH 里捡到别的编译器;CMAKE_BUILD_TYPE=Release让默认优化级别生效;--build build -j 8并行编译,核数按机器调整。路径里用正斜杠,反斜杠在 CMake 参数里容易被当转义符。
如果是手写 Makefile,注意make命令的实际名字是mingw32-make.exe,很多教程直接写make,而 Windows 上并没有这个可执行文件。可以在bin目录下复制一份改名为make.exe,或者直接用全名。
提示:如果 CMake 报
CMAKE_C_COMPILER not found,先单独在终端里执行一遍gcc -v确认命令行可用,再回来跑 CMake,九成是 PATH 或者路径拼写问题。
4.3 在 IDE 里绑定这套工具链
用 VS Code 的话,在.vscode/c_cpp_properties.json里把编译器路径和标准库路径都写死,别依赖自动探测:
{ "configurations": [ { "name": "MinGW-13.1.0", "compilerPath": "D:/dev/mingw64/bin/g++.exe", "intelliSenseMode": "windows-gcc-x64", "cStandard": "c17", "cppStandard": "c++20" } ], "version": 4 }compilerPath指向具体可执行文件,intelliSenseMode选 gcc 系列,标准版本与编译命令保持一致。这样头文件补全和跳转才会走这套工具链的include目录,否则编辑器和编译器各看各的头文件,会出现「编辑器不报错、编译报错」的割裂感。其他 IDE 思路一致:找到编译器路径、标准库路径、调试器路径三项,全部指向同一个根目录。
5. 避坑与排查:五条装 MinGW 时被反复踩的坑
这一节全部来自实际装环境时遇到的现场,按「现象 → 原因 → 解决」记,遇到对应症状直接对号入座。
5.1 编译通过但运行报缺 DLL
现象:g++编译成功,双击 exe 或者换台机器运行,弹窗提示找不到libstdc++-6.dll或libgcc_s_seh-1.dll。
原因:动态链接的标准库 DLL 位于D:\dev\mingw64\bin下,本机靠 PATH 能找到所以能跑;换环境后 PATH 里没有这个目录,就找不到。另外,如果系统里存在多个版本的libstdc++-6.dll,程序可能加载到旧的那份,直接崩溃。
解决:分发时用-static参数静态链接标准库,命令是g++ -static -static-libgcc -static-libstdc++ main.cpp -o app.exe。或者把bin下需要的 DLL 拷到 exe 同目录。两种方式各有取舍:静态链接体积大但省事,动态拷贝体积小但容易漏文件。
5.2 版本号显示正确但标准库头文件缺失
现象:gcc -v显示 13.1.0,可编译#include <format>或<ranges>时报「No such file or directory」。
原因:PATH 命中的是 13.1.0 的bin,但实际调用的include目录不是这套的,常见于系统里同时装了两套 MinGW 和一个 MSYS2,头文件路径被别的工具链抢先。
解决:用g++ -v -E -x c++ -打印当前生效的头文件搜索路径,看第一顺位的include/c++/13.1.0是不是指向你的解压目录。不是的话,回到 3.2 节修 PATH 优先级,同时检查有没有残留的CPATH、C_INCLUDE_PATH环境变量,这两个变量会额外叠加搜索路径,是隐藏的干扰源。
5.3 命令行能编译,IDE 里一直报红
现象:终端里g++一切正常,VS Code 或某 IDE 的编辑器里满屏波浪线,说找不到iostream。
原因:编辑器的 IntelliSense 走的是另一套配置,和终端 PATH 无关。它可能还记着安装前的编译器,或者自动探测失败了。
解决:按 4.3 节显式写compilerPath,写完执行一次「重置 IntelliSense 数据库」命令。如果还不行,检查工作区里有没有旧的.vscode配置残留,删掉重新生成比逐个参数对更快。
5.4 make 命令不存在或报错
现象:跟着教程敲make,提示「无法将 make 项识别为 cmdlet」。
原因:MinGW-w64 自带的构建工具叫mingw32-make.exe,不叫make.exe;另外如果你装了 Git for Windows,它自带的make也可能被优先命中,行为和 MinGW 不完全一致。
解决:统一用mingw32-make,或者在bin目录复制一份改名为make.exe。执行where make确认命中的是哪一个,避免两个 make 混用导致规则解析差异。
5.5 中文路径与空格导致的诡异链接错误
现象:编译单个文件没问题,链接时报cannot find -lxxx或file not found,把项目挪个位置就好了。
原因:路径里有中文、空格或者特殊字符,MinGW 的部分工具对这类路径处理不完善,尤其在带-I、-L、-l参数时更容易暴露。
解决:工程目录和工具链目录都放在纯英文、无空格路径下,比如D:\dev\mingw64和D:\work\demo。这条看似啰嗦,但它能消掉一整类无法从报错信息看出根因的问题。
6. 装完怎么验证:用一条命令确认这套工具链真的能用
验证不是看到版本号就完事,版本号只证明bin里的驱动程序存在,不证明标准库、头文件、异常模型、运行库都对。我一般会跑一个「四合一」的探针程序,覆盖 C++20 标准库、线程、文件系统、异常展开四条链路,一次跑通才算装好。
# probe.cpp:四合一验证,任一环节缺失都会在编译或运行期暴露 g++ -v -E -x c++ - < nul # 先打印头文件搜索路径 # 再编译下面这段#include <iostream> #include <thread> #include <filesystem> #include <stdexcept> int main() { // 1) 标准库版本与编译器版本 std::cout << __VERSION__ << "\n"; // 2) 线程模型:能正常创建线程说明 posix 生效 std::thread t([]{ std::cout << "thread ok\n"; }); t.join(); // 3) 文件系统库 std::cout << std::filesystem::temp_directory_path() << "\n"; // 4) 异常展开:seh 模型下 try/catch 正常工作 try { throw std::runtime_error("seh ok"); } catch (const std::exception& e) { std::cout << e.what() << "\n"; } return 0; }编译:g++ -std=c++20 -O2 probe.cpp -o probe.exe -pthread。
判定标准很明确:输出__VERSION__里含13.1.0;打印thread ok(线程模型对);打印一个临时目录路径(文件系统库对);打印seh ok(异常展开对)。四项全过,说明这套x86-64-13.1.0-release-posix-seh-ucrt-rt-v11-r是完整可用的,接下来再拿真实项目跑一遍cmake就足够放心了。
还有两件小事值得养成习惯。一是每装完一套工具链,把gcc -v的完整输出存一份到笔记里,将来项目出问题可以直接比对环境差异,这比凭记忆回忆「当时装的是啥版本」靠谱得多。二是发布程序前一律用-static编一次验证,静态链接能跑通基本就排除了 DLL 路径问题,这是后悔药里最便宜的一颗。
从那以后我每次接手新机器,第一件事不是装 IDE,而是先把这个探针程序跑一遍,四项全绿才开始 clone 项目。多花五分钟,省掉的是半天查不到根因的调试。希望帮到你。
本文还有配套的精品资源,点击获取