简介:MinGW64 x86-64 15.2.0 是面向 64 位 Windows 平台的完整开源 C/C++ 开发工具链,适合需要在 Windows 下进行原生编译、并兼顾 Unix/Linux 跨平台兼容项目的开发者。该版本引入 SEH 结构化异常处理与 UCRT 通用 C 运行时,并包含针对 Visual Studio 2019 的 RT v13 修订,在异常处理、库函数支持与运行时兼容性上均有改进,可配合 GCC、G++ 及调试、构建等辅助工具使用。资源包为 rar 格式,共约 2000 个文件,整体约 173.71MB,以 967 个 h 头文件、761 个 py 脚本、243 个 hpp 头文件为主,另含少量 txt 说明、sh 脚本及 css、html、json、js、c、md 等文件,覆盖头文件依赖、构建脚本与文档说明等用途。目前已有 783 人学习下载。对于希望快速搭建稳定开发环境、减少 GitHub 下载等待时间的用户,这份工具集可直接用于编译、调试与项目构建,帮助专注代码编写而非工具兼容性问题。
1. MinGW64 15.2.0 这个包到底装了什么:从文件名拆到适用场景
很多人第一次看到mingw64-x86-64-15.2.0-release-win32-seh-ucrt-rt-v13-rev0这串名字,第一反应是“这不就是个压缩包吗”,然后随手解压丢进某个目录,配完 PATH 就再也没管过。直到某天编译一个带std::thread的 C++ 程序,链接阶段报出一堆undefined reference to __imp___acrt_iob_func,或者跑起来直接弹窗提示缺libwinpthread-1.dll,才回头研究这串名字到底在说什么。这个包本质上是 MinGW-w64 工具链在 Windows 上的一个完整发行版,基于 GCC 15.2.0,目标架构 x86-64,线程模型 win32,异常模型 seh,C 运行时用 ucrt,还带了运行时库和 v13 修订标记。它解决的核心问题是:让你在 Windows 上不依赖 Visual Studio 就能编译出原生 64 位可执行文件,同时避开 MSYS2 那套包管理器的学习成本。适合谁?适合需要快速搭一个干净 C/C++ 编译环境的人,比如刷算法题、写小工具、交叉编译第三方库,或者给 CI 流水线塞一个可复现的编译器。不适合谁?不适合需要完整 POSIX 环境、依赖 autotools 复杂构建系统的人,那种场景还是 MSYS2 或 WSL 更省心。
2. 文件名逐段解码:win32、seh、ucrt 到底怎么选
2.1 线程模型 win32 与 posix 的取舍
线程模型决定std::thread、std::mutex这些标准库设施底层怎么实现。win32 模型直接调用 Windows 原生线程 API,posix 模型则依赖一个 pthread 兼容层。选 win32 的好处是生成的二进制不依赖libwinpthread-1.dll,分发时少一个文件;坏处是某些依赖 pthread 语义的库(比如老版本 libstdc++ 的std::condition_variable实现)在极端并发场景下行为可能有细微差异。常见做法是:如果你只写纯标准 C++ 且不链接第三方 pthread 库,win32 足够;如果你要编译像 FFmpeg、GStreamer 这类内部大量使用 pthread 的项目,posix 模型更稳。这个包选的是 win32,意味着它追求的是“轻依赖、易分发”。
2.2 异常模型 seh 与 sjlj、dwarf 的区别
异常模型决定 C++ 异常和结构化异常处理怎么落地。seh 是 Windows 原生结构化异常处理,零开销(不抛异常时几乎无性能损失),但只支持 64 位目标。sjlj 是 setjmp/longjmp 实现,跨平台但每次进入 try 块都有开销。dwarf 是 DWARF 调试信息驱动的异常,性能好但 32 位 Windows 上兼容性差。对于 x86-64 目标,seh 是首选,没有理由退到 sjlj。这个包选 seh 是正确的默认。如果你看到某个包在 64 位下还用 sjlj,那通常是历史遗留或为了兼容极老的系统。
2.3 C 运行时 ucrt 与 msvcrt 的迁移
ucrt 是 Universal C Runtime,从 Windows 10 开始成为系统组件,Windows 7/8 需要打补丁。msvcrt 是旧版 Visual C++ 运行时,兼容性广但功能陈旧。选 ucrt 意味着你用的是现代 C 标准库实现,printf、fopen这些函数的行为更接近标准,且与 Visual Studio 2015 之后的运行时二进制兼容。代价是目标机器不能太老。常见做法是:面向 Windows 10 及以上,无脑选 ucrt;如果要支持 Windows 7 且不想让用户装补丁,才考虑 msvcrt。这个包选 ucrt,定位很明确——现代 Windows 开发。
2.4 版本号 15.2.0 与 rev0 的含义
15.2.0 是 GCC 版本,rev0 是发行修订号。GCC 15.x 对 C++23 的支持已经相当完整,std::print、std::format这些都能用。rev0 表示这是该版本的首个打包修订,通常意味着没有额外的补丁集。如果你在别处看到 rev1、rev2,那是在 rev0 基础上修了打包脚本或补了运行时文件。选 rev0 还是 revN 取决于你是否遇到了已知的打包问题,一般新装直接用最新修订。
3. 从零跑通第一个可执行文件:解压、配 PATH、验证
3.1 解压位置与目录结构
不要解压到带空格或中文的路径,这是血泪经验。C:\mingw64是最省事的。解压后你会看到bin、lib、include、libexec、share等目录。bin下面是gcc.exe、g++.exe、gdb.exe、mingw32-make.exe这些。lib下面是libstdc++-6.dll、libgcc_s_seh-1.dll、libwinpthread-1.dll等运行时。include是 C/C++ 头文件。libexec里是编译器内部用的程序,比如cc1plus.exe。
# 假设解压到 C:\mingw64,在 PowerShell 里临时把 bin 加进 PATH $env:Path = "C:\mingw64\bin;" + $env:Path # 验证 gcc 版本 gcc --version # 验证 g++ 版本 g++ --version # 验证 gdb 是否可用 gdb --version逻辑说明:$env:Path是 PowerShell 的临时环境变量,只对当前会话生效,不会污染系统。gcc --version输出里应该能看到15.2.0和x86_64-w64-mingw32字样。如果提示“不是内部或外部命令”,说明 PATH 没配对,或者解压路径不对。参数说明:--version是 GCC 的标准选项,打印版本和配置信息,配置信息里会显示Thread model: win32和Exception model: seh,可以用来确认你拿到的包和文件名一致。
3.2 编译一个带线程和异常的最小程序
// test_thread_exception.cpp #include <iostream> #include <thread> #include <vector> #include <stdexcept> void worker(int id) { // 模拟一点工作 for (int i = 0; i < 3; ++i) { std::cout << "thread " << id << " step " << i << "\n"; } if (id == 2) { throw std::runtime_error("thread 2 failed on purpose"); } } int main() { std::vector<std::thread> threads; for (int i = 0; i < 4; ++i) { threads.emplace_back(worker, i); } for (auto& t : threads) { if (t.joinable()) { t.join(); } } std::cout << "main done\n"; return 0; }编译命令:
g++ -std=c++23 -O2 -Wall -Wextra -o test_thread_exception.exe test_thread_exception.cpp逻辑说明:-std=c++23启用 C++23 标准,GCC 15.2.0 支持得不错。-O2开优化,-Wall -Wextra开警告。这个程序会启动 4 个线程,其中线程 2 抛异常。注意:在 win32 线程模型下,线程函数里抛出的异常如果没有在线程内捕获,会直接调用std::terminate,整个进程挂掉。这是标准行为,不是 bug。如果你想验证异常传播,应该在线程函数里 try-catch,或者用std::async拿 future。参数说明:-o指定输出文件名,不加-o默认是a.exe。-Wall -Wextra建议常开,能提前发现很多问题。
3.3 运行与依赖检查
# 直接运行 .\test_thread_exception.exe # 查看依赖的 DLL objdump -p test_thread_exception.exe | findstr "DLL Name"逻辑说明:objdump是 binutils 里的工具,-p打印 PE 文件头信息,findstr过滤出 DLL 名称。你应该能看到KERNEL32.dll、msvcrt.dll或ucrtbase.dll、libstdc++-6.dll、libgcc_s_seh-1.dll。如果看到libwinpthread-1.dll,说明你拿到的包其实是 posix 线程模型,和文件名不符。参数说明:findstr是 Windows 自带的文本过滤工具,类似 grep。如果你在 Git Bash 里,可以用grep。
提示:如果运行时提示缺
libstdc++-6.dll,说明C:\mingw64\bin不在 PATH 里,或者你把这个 exe 拷到了别的机器。分发时要么带上这几个 DLL,要么用-static静态链接。
4. 静态链接与动态链接:分发时少踩 DLL 缺失的坑
4.1 静态链接的编译参数
g++ -std=c++23 -O2 -Wall -Wextra -static -static-libgcc -static-libstdc++ -o test_static.exe test_thread_exception.cpp逻辑说明:-static让链接器尽量静态链接所有库,-static-libgcc和-static-libstdc++分别针对 GCC 运行时和 C++ 标准库。这样生成的 exe 不依赖libstdc++-6.dll和libgcc_s_seh-1.dll,但可能仍然依赖ucrtbase.dll(系统组件,Windows 10 自带)。参数说明:静态链接会让 exe 体积变大,通常从几十 KB 涨到几 MB,但换来的是“拷到哪都能跑”。如果你用了-static还提示缺 DLL,检查是不是链接了第三方动态库。
4.2 动态链接时该带哪些 DLL
如果你不想静态链接,分发时至少带上libstdc++-6.dll、libgcc_s_seh-1.dll。如果用了 win32 线程模型,不需要libwinpthread-1.dll。如果用了 posix 线程模型,必须带上。常见做法是:在C:\mingw64\bin里找到这几个 DLL,和 exe 放同一目录。Windows 加载 DLL 的顺序是先找 exe 所在目录,所以放一起最省事。
# 查看 exe 依赖哪些非系统 DLL objdump -p test_thread_exception.exe | findstr "DLL Name" | findstr /V "KERNEL32 msvcrt ucrtbase ADVAPI32"逻辑说明:findstr /V是反向过滤,排除系统 DLL,剩下的就是你需要手动分发的。参数说明:不同程序依赖的系统 DLL 不同,常见的还有USER32.dll、GDI32.dll、SHELL32.dll,这些都不用管。
4.3 用 CMake 管理构建时的工具链配置
# CMakeLists.txt cmake_minimum_required(VERSION 3.20) project(mingw_test CXX) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_executable(test_thread_exception test_thread_exception.cpp) if(MINGW) target_link_options(test_thread_exception PRIVATE -static-libgcc -static-libstdc++) endif()逻辑说明:if(MINGW)判断当前编译器是不是 MinGW,是的话加静态链接选项。target_link_options是 CMake 3.13 之后推荐的写法。参数说明:CMAKE_CXX_STANDARD 23设置 C++ 标准,CMAKE_CXX_STANDARD_REQUIRED ON表示如果编译器不支持就报错而不是降级。生成构建系统时用cmake -G "MinGW Makefiles" ..,然后mingw32-make。
5. 避坑与排查:那些让编译突然翻车的细节
5.1 现象:编译时报cannot find -lstdc++
原因:链接器找不到libstdc++.a或libstdc++.dll.a。通常是解压不完整,或者lib目录被误删。解决:重新解压,确认C:\mingw64\lib下有libstdc++.a、libstdc++.dll.a、libgcc.a等文件。如果用的是-static-libstdc++,需要libstdc++.a存在。
5.2 现象:运行时报The program can't start because libwinpthread-1.dll is missing
原因:你拿到的包其实是 posix 线程模型,或者你链接了某个依赖 pthread 的第三方库。解决:从C:\mingw64\bin拷贝libwinpthread-1.dll到 exe 同目录,或者改用 win32 线程模型的包。如果你确认文件名是 win32 但仍然缺这个 DLL,检查是不是第三方库引入的。
5.3 现象:gdb启动后无法加载符号,或者断点不生效
原因:编译时没加-g,或者加了-s剥离了符号。解决:调试版本用g++ -g -O0 -o test_d.exe test.cpp。-O0关优化,避免变量被优化掉。-g生成调试信息。如果还不行,检查gdb版本和 GCC 版本是否匹配,这个包自带的gdb应该能直接用。
5.4 现象:中文路径下编译失败,报错里带乱码
原因:MinGW 工具链对非 ASCII 路径支持不好,尤其是make和gcc调用子程序时。解决:项目路径全用英文,不要有空格和中文。这是最省事的办法。如果必须用中文路径,试试在MSYS2环境下跑,或者用-fexec-charset=GBK之类的选项,但效果不稳定。
5.5 现象:std::filesystem链接报错undefined reference to std::filesystem::...
原因:GCC 9 之前需要额外链接-lstdc++fs,GCC 15 已经不需要了。如果你遇到这个错,说明你可能混用了老版本的头文件或库。解决:确认g++ --version是 15.2.0,确认没有手动加-lstdc++fs。如果是从别的项目拷贝的 CMake 配置,把stdc++fs删掉。
6. 进阶技巧:用这个包交叉编译第三方库与验证 ABI 兼容性
6.1 交叉编译一个简单的 C 库
假设你有一个mylib.c和mylib.h,想编译成静态库和动态库。
# 编译成目标文件 gcc -c -O2 -Wall -Wextra -o mylib.o mylib.c # 打包成静态库 ar rcs libmylib.a mylib.o # 编译成动态库 gcc -shared -O2 -Wall -Wextra -o mylib.dll mylib.c -Wl,--out-implib,libmylib.dll.a逻辑说明:ar rcs是创建静态库的标准命令,r插入,c创建,s写索引。-shared生成动态库,-Wl,--out-implib让链接器同时生成导入库,这样别人链接-lmylib时能找到。参数说明:-Wl,是给链接器传参,--out-implib指定导入库文件名。如果你不生成导入库,别人只能动态加载mylib.dll,不能直接链接。
6.2 验证 C++ ABI 兼容性
MinGW-w64 的 C++ ABI 和 MSVC 不兼容,这是常识。但同一个 MinGW-w64 发行版内部,不同版本之间也可能有 ABI 变化。验证方法是:用旧版本编译一个导出 C++ 类的 DLL,用新版本编译一个调用它的 exe,看能不能链接和运行。
// lib_export.cpp #include <string> class __declspec(dllexport) Greeter { public: std::string greet(const std::string& name); }; std::string Greeter::greet(const std::string& name) { return "hello " + name; }g++ -shared -o lib_export.dll lib_export.cpp -Wl,--out-implib,lib_export.dll.a然后用另一个版本的工具链编译调用方:
// main_call.cpp #include <iostream> #include <string> class __declspec(dllimport) Greeter { public: std::string greet(const std::string& name); }; int main() { Greeter g; std::cout << g.greet("mingw") << "\n"; return 0; }g++ -o main_call.exe main_call.cpp -L. -l_export逻辑说明:如果两个版本 ABI 兼容,链接和运行都会成功。如果报undefined reference或运行时崩溃,说明 ABI 不兼容。参数说明:__declspec(dllexport)和__declspec(dllimport)是 Windows 特有的导出导入声明。-L.指定库搜索路径为当前目录,-l_export链接lib_export.dll.a。
6.3 用nm和objdump检查符号
# 查看静态库里的符号 nm libmylib.a # 查看动态库导出的符号 objdump -p mylib.dll | findstr "Export" # 查看 exe 引用的符号 nm -u main_call.exe逻辑说明:nm列出目标文件或库里的符号,-u只显示未定义符号。objdump -p显示 PE 结构,Export段是导出的函数。参数说明:这些工具在C:\mingw64\bin下都有。如果你看到__imp_前缀的符号,那是导入符号,正常。
6.4 一个我常用的验证习惯
每次拿到一个新的 MinGW-w64 包,我不会直接上大项目,而是先跑一个“三件套”:编译一个带线程和异常的 C++ 程序、编译一个静态库并链接、用gdb跑一遍断点。这三步过了,基本能确认工具链完整、线程模型和异常模型符合预期、调试器可用。从那以后我每次换工具链都强制走一遍这个流程,省得在项目里翻车。希望帮到你。
本文还有配套的精品资源,点击获取