简介:MinGW GCC 12.2.0 是面向 Windows 64 位平台的完整 GCC 编译工具链,适合希望在 Windows 上编写 C/C++ 且不想依赖 Visual Studio 的开发者使用。该版本支持 C++20 等新标准,可与 CMake 配合完成跨平台构建,并提供 gcc、g++ 及配套调试工具。压缩包约 68 MB,共约 2000 个文件,以 h 头文件、a/lib 静态库、py/pyc 脚本及 HTML 文档为主,同时包含 exe/dll 运行组件,目录结构基本覆盖编译、链接与辅助调试所需内容。已有 393 人学习下载。借助这套环境,可快速配置命令行编译环境、调用最新语言特性,并结合 CMake 管理项目、使用 GDB 定位问题,适合初学者搭建开发环境,也适合需要独立工具链的中高级开发者。
1. 为什么是 12.2.0 而不是 13、14:MinGW 选型的现实逻辑
很多人第一次接触 MinGW GCC,是在 Windows 上用 VS Code 写 C/C++ 报出“gcc 不是内部或外部命令”,然后搜索“mingw官网下载”,稀里糊涂装了个版本就开始编译。但如果你要正经做一个 Windows 下的 C/C++ 构建环境,版本选择这件事值得先花两分钟想清楚。
MinGW-w64 的 GCC 12.2.0 是 2022 年下半年发布的稳定分支,既不像 11.x 那样在 C++23 特性上捉襟见肘,也不像 13.x、14.x 那样需要频繁应对头文件路径和标准库 ABI 的调整。我在实际项目里从 11.x 迁到 12.2.0 后,最大的感受是它的 libstdc++ 对 C++20 的支持已经从“能用”变成“好用”,而第三方库的预编译包大多也跟上了这个版本。适合的人群很明确:在 Windows 上用 MinGW 工具链做 C/C++ 桌面程序、CLI 工具或嵌入式上位机,同时希望编译器版本相对保守、不追新的人。
这篇笔记会把从下载、安装、环境变量到实际编译的关键步骤全部过一遍,并且把我这几年在 MinGW-w64 GCC 12.2.0 上踩过的坑、调过的参数、误报过毒的问题都交代清楚。
2. 安装与首次编译:装到能用共分几步
2.1 选哪个发行版:MinGW-w64 的“官方”问题
这里的第一个坑就是“mingw官网下载”到底指哪个官网。如果你搜到的是老旧的 mingw.org,那对应的是 32 位 GCC,版本停留在 4.x,基本没法用。真正活跃的是 MinGW-w64 项目,它在 Windows 下的常见获取途径是开源的构建产物仓库,比如 winlibs.com 或 niXman 的 GitHub releases。GitHub 下载慢的问题,用代理镜像站点或国内镜像加速即可,核心是不要下错架构。
我一般会选 winlibs 的“UCRT runtime”版本,而不是老旧的 MSVCRT 版本。UCRT(Universal C Runtime)是微软随 Windows 10 SDK 提供的 C 运行时,支持更完整的 C99/C11 标准库,比如printf的 long double 行为、%zu格式符等。12.2.0 的 UCRT 版本在 Win10/11 上开箱即用,不用额外装运行库。
提示:GCC 12.2.0 有 POSIX 和 Win32 线程模型两个分支。POSIX 版本可以用
std::thread、std::mutex的完整 C++ 标准库实现,Win32 版本只支持基础的std::thread封装。C++ 多线程代码选 POSIX,纯 C 代码选 Win32 体积稍小。
2.2 拿到压缩包之后:目录结构与环境变量
winlibs 的发布包是 zip 格式,没有安装向导。解压到比如C:\mingw64后,你会看到bin、include、lib、libexec等目录。bin里既有gcc.exe、g++.exe和gfortran.exe,还有mingw32-make.exe、gdb.exe这些配套工具。
接下来配置环境变量。以 Win10/11 为例,在“系统属性 → 环境变量”里新建用户变量MINGW_HOME=C:\mingw64,然后把%MINGW_HOME%\bin追加到Path。追加 Path 这一步是新手最容易漏的,漏掉的直接后果就是在任意目录敲gcc提示不是内部或外部命令。
环境变量配好后,在全新的 PowerShell 或 CMD 窗口里验证:
gcc --version gcc -v -E -x c /dev/null第一条命令确认版本号是gcc.exe (MinGW-W64 GCC-12.2.0)这样的格式,第二条能看到Target: x86_64-w64-mingw32、Thread model: posix、Supported LTO compression algorithms等关键编译配置。看到gcc version 12.2.0就说明编译器核心可用。
2.3 最小编译链路:从 hello.c 到 hello.exe
用记事本或 VS Code 写一个最小的测试文件:
#include <stdio.h> int main(void) { printf("mingw gcc 12.2.0 is working\n"); return 0; }在文件所在目录打开终端,执行:
gcc hello.c -o hello.exe ./hello.exe这行命令的简单之处在于它隐藏了三个关键事实:gcc会自动调用cc1完成词法与语法分析、生成汇编;调用as把汇编转成目标文件;调用collect2和ld完成链接,并自动带上libmingw32.a、libmsvcrt.a或 UCRT 对应的导入库。-o hello.exe指定输出文件名,这里显式写出.exe后缀可以避免在 PowerShell 里执行时遇到路径冲突。
如果这段代码编译出来直接能跑,说明你的 MinGW-w64 12.2.0 基础链路已经通了。接下来再看两个更接近实际工程的编译命令。
2.4 静态链接与动态链接:为什么你总是需要-static-libgcc
在 Windows 上分发程序时,最怕的就是目标机器缺少运行库。MinGW-w64 12.2.0 默认会动态链接libgcc_s_seh-1.dll、libstdc++-6.dll和libwinpthread-1.dll(POSIX 线程模型才有)。这三个 DLL 在你自己的开发机上当然存在于bin目录,但用户机器上没有。
我一般直接用全静态链接命令:
g++ main.cpp -o app.exe -static-libgcc -static-libstdc++ -static-static-libgcc把 GCC 内部支持库打进可执行文件,-static-libstdc++把 C++ 标准库打进去,最后一个-static则尽量把所有链接库都静态化。这样得到的app.exe里就看不到 winpthread-1.dll 这类外部依赖了。
注意:
-static是全局静态的标志,它会让链接器优先选择.a而非.dll.a。但对于 OpenGL、Win32 API 这类系统库,本来就有导入库可用,静态化不会造成问题。
用objdump -p app.exe | findstr "DLL Name"命令查看依赖列表,如果只剩KERNEL32.dll和UCRTBASE.dll这类系统 DLL,就说明静态化成功了。这一步在做 CMake 工程时尤其重要,因为 CMake 的默认行为经常悄悄引入动态链接。
3. 架构选择与编译参数调优:把工具链调成你要的样子
3.1 i686 还是 x86_64:一个影响 ABI 的决策
MinGW-w64 的发布包分为 32 位(i686)和 64 位(x86_64)。64 位 GCC 12.2.0 默认使用x86-64指令集,并且支持-march和-mtune参数优化。32 位版本则默认使用i586级别的指令集。
实际项目里,如果目标机器都是 64 位 Windows,直接选 64 位。如果有兼容 32 位老机器的需求,就得同时装两套工具链。注意同一个 MinGW 安装目录下不要混装两个架构,否则gcc可能链接到错误位数的库,报出skipping incompatible ...错误。
架构确认命令:
gcc -dumpmachine输出x86_64-w64-mingw32说明是 64 位工具链。如果输出i686-w64-mingw32,则说明是 32 位。
3.2 必调的三个编译参数:优化、例外处理与栈检查
GCC 12.2.0 在 Windows 上的默认编译参数对桌面应用来说太保守了。我通常会在 Makefile 或 CMake 里加入以下组合:
CXXFLAGS = -O2 -march=x86-64-v2 -msse4.2 -mfpmath=sse -fno-exceptions -fno-rtti LDFLAGS = -static-libgcc -static-libstdc++ -static-march=x86-64-v2是 GCC 12 新增的指令集级别,覆盖了 Nehalem 及之后大多数现代 CPU 的 SSE4.2、POPCNT 指令,比默认的x86-64基准确认有约 5% 到 10% 的整数运算提升。-mfpmath=sse对于 x86 平台意义更大,它让浮点运算走 SSE 寄存器而不是 x87 栈,结果是浮点行为更接近 Linux 上的 GCC。-fno-exceptions和-fno-rtti只适用于纯 C 模块或不需要 C++ 异常的项目,能明显减小二进制体积。带了 C++ 标准库的代码不要用这两个参数,否则链接阶段会报未定义引用。
这里要特别说明-O2和-Os的选择:如果做的是桌面工具,-O2的启动速度更快;如果做的是体积敏感的嵌入式上位机,-Os能把镜像大小缩小 10% 左右。GCC 12 的-Os实现比旧版本稳得多,不会为了省空间做过于激进的变换。
3.3 静态与共享库的生成:从 .a 到 .dll
在 Windows 上用 MinGW 生成本地动态库的语法和 Linux 上有区别。生成 DLL 的常见做法如下:
gcc -shared -o mylib.dll mylib.c -Wl,--output-def,libmylib.def -Wl,--out-implib,libmylib.dll.a -Wl,--export-all-symbols-shared指示生成 DLL。-Wl,--output-def,libmylib.def让链接器同时导出 DEF 文件。这个文件在生成导入库时很有用,特别是给 MSVC 进行链接时(后面专门讲)。-Wl,--export-all-symbols自动导出所有全局符号。如果希望只暴露特定接口,就应该去掉这个参数,用一个.def文件显式列出导出函数名。
如果生成的是 C++ 动态库,记得在头文件里加__declspec(dllexport)和__declspec(dllimport)的宏包装,否则-fvisibility=hidden参数会让大部分符号在 DLL 外部不可见。MinGW 12.2.0 对 C++ 的可见性控制已经完全对齐 GCC 的 ELF 语义,-fvisibility=hidden与__attribute__((visibility("default")))的组合在 Windows 上是有效的。
手写一个简单的 DEF 文件:
LIBRARY mylib.dll EXPORTS add_numbers subtract_numbers相比自动导出,显式 DEF 文件的好处是导出函数名稳定,不会因为编译选项变化而出现 C++ name mangling 差异。
4. MinGW GCC 12.2.0 避坑与排查:从安装失败到误报毒
4.1 安装后 gcc 版本不变:老版本优先级的坑
现象:明明已经安装了 12.2.0,在终端敲gcc --version,反馈仍是旧版本,例如 8.1.0,但直接执行完整路径C:\mingw64\bin\gcc.exe --version显示的是 12.2.0。
原因:系统Path里存在多个 MinGW 或 GCC 安装路径,比如旧版 CodeBlocks 自带的C:\Program Files\CodeBlocks\MinGW\bin,或 VS Code 自动拉取的 MSYS2 路径。Windows 按Path顺序搜索 exe,前面的先命中。
解决:在系统环境变量里,把%MINGW_HOME%\bin提到所有其他可能包含gcc.exe的路径之前。修改后必须重启终端,老进程的环境变量不会自动刷新。也可以在 PowerShell 里输入where.exe gcc查看实际命中的路径,按输出顺序反推是谁占用了前面的位置。
4.2 编译时头文件缺失:interix 路径与系统 include 混扰
现象:源码在 Linux 上编译正常,换到 MinGW 12.2.0 报stdio.h: No such file or directory或bits/c++config.h not found。
原因:MinGW 的 include 检索顺序与 Linux 不同,默认缺少/usr/include的概念。如果你在环境变量里设置了CPATH或C_INCLUDE_PATH指向了 Linux 风格的目录,或者把 MSYS2 的根目录混入 Path,就可能导致 GCC 在错误的路径下寻找标准头文件。
解决:先执行gcc -v -E -x c /dev/null,观察输出中#include "..."和#include <...>的搜索路径列表。确认第一项是C:\mingw64\lib\gcc\...\include,第二项是C:\mingw64\include。如果出现/usr/include或/mingw64/include,说明CPATH或某个 msys2 环境变量干扰了。临时清空CPATH再做验证。
4.3 杀毒软件误报 gcc.exe:现实且高频的困扰
现象:解压下来的gcc.exe、ld.exe或生成的.exe被 Windows Defender 或第三方杀毒软件直接隔离。尤其是小型独立程序,报毒名称五花八门。
原因:GCC 编译出来的 PE 文件有时包含可写可执行段,且代码入口表带明显的压缩特征;MinGW 的binutils生成的导入表结构和系统库相似,部分杀软引擎会把“能执行代码的 DLL 同包发布”视为潜在恶意行为。
解决:做开发时在 Windows Defender 的排除项里添加 MinGW 的根目录和你的构建输出目录。这不算后门,是本地开发环境的常规处理。此外,尽量使用官方渠道发布的 MinGW-w64 构建包,第三方魔改版本更容易被杀软标记。还有一个小技巧是给最终可执行文件加签名或 UPX 压缩后加壳,但这通常只是让误报概率降低,不能根治。
4.4 下载网速过慢:镜像与断点续传技巧
现象:从 winlibs 或 GitHub 下载 MinGW 12.2.0 压缩包速度只有几十 KB/s,甚至中途失败。
原因:GitHub 的 release 附件默认走 CDN,在国内部分地区连接不稳定。这是网络链路问题,不是工具链问题。
解决:优先选择国内镜像站,比如部分高校的镜像页面会同步 MinGW-w64 和 winlibs 的构建产物。也可以用支持断点续传的下载工具拉取,避免一次性的浏览器下载中断。下载完成后校验压缩包哈希,winlibs 页面会同时给出 SHA256,这一步不要跳过,压缩包损坏会在解压时产生不可预期的缺失文件错误。
4.5 make 命令不存在:mingw32-make 和 make 的差异
现象:按习惯执行make,提示命令不存在。按 VS Code 教程执行mingw32-make又可以。
原因:MinGW-w64 的 bin 目录里只有mingw32-make.exe,没有make.exe。近年来很多教程直接用mingw32-make命名,但它只对应 GNU Make 的一个特定构建变量集。如果从 Linux 迁移代码,Makefile 里用到$(CC)、$(CFLAGS)等变量不受影响,但用到make clean的默认规则时需要确认名称。
解决:习惯上我用mingw32-make.exe并给它做一个名为make.exe的软链接,或者直接设置MAKE=mingw32-make。要注意部分项目会把make硬编码进脚本,这种情况下复制一个make.exe最简单。
5. 从命令行到构建脚本:把 GCC 12.2.0 接进实际工程
5.1 手动编译多文件工程:一组可复制的命令
中小型项目不需要立刻上 CMake,用 GCC 直接编译也是合理的。假设工程结构如下:
src/main.c src/utils.c src/network.c include/utils.h include/network.h手动编译的完整命令序列:
gcc -O2 -Wall -Wextra -Iinclude -c src/main.c -o build/main.o gcc -O2 -Wall -Wextra -Iinclude -c src/utils.c -o build/utils.o gcc -O2 -Wall -Wextra -Iinclude -c src/network.c -o build/network.o gcc build/main.o build/utils.o build/network.o -o bin/app.exe -lws2_32最后一行链接时加了-lws2_32,这是 Windows 下网络编程必需的 Socket 库,对应系统里libws2_32.a导入库。如果不加,WSAStartup、socket等函数会报 undefined reference。
这种手动编译方式适合最多几十个源文件的小工程。文件多了以后,增量编译的依赖关系管理会成为灾难,这时候就需要 Makefile 或 CMake。
5.2 与 CMake 的协作:指定工具链为默认编译器
Visual Studio Code 里配好 MinGW 后,CMake 经常不能自动识别到 GCC 12.2.0,尤其是系统里同时装着 MSVC 的情况下。CMake 会按注册表优先找 Visual Studio,而非Path里的 gcc。
最可靠的方式是在 CMAKE 命令行显式指定编译器:
cmake -S . -B build -G "MinGW Makefiles" -DCMAKE_C_COMPILER=C:/mingw64/bin/gcc.exe -DCMAKE_CXX_COMPILER=C:/mingw64/bin/g++.exe-G "MinGW Makefiles"指示 CMake 生成可供 mingw32-make 使用的 Makefile。-DCMAKE_C_COMPILER与-DCMAKE_CXX_COMPILER直接写入全路径,绕开 CMake 自动探测。
CMake 第一次配置成功后,后续只要执行cmake --build build即可。CMake 会自动调用mingw32-make,不需要手动设置-j,因为 CMake 的并行度参数是独立的-- -j4。
5.3 vscode 里写代码时的配置:tasks.json 与 launch.json
在 VS Code 里用这款编译器做 C/C++ 开发,核心配置有两个文件。tasks.json负责编译,launch.json负责调试。下面是一组我常用的配置片段:
{ "version": "2.0.0", "tasks": [ { "type": "cppbuild", "label": "gcc build active file", "command": "C:/mingw64/bin/gcc.exe", "args": [ "-g", "-o", "${fileDirname}/${fileBasenameNoExtension}.exe", "${file}" ], "group": "build", "problemMatcher": ["$gcc"] } ] }-g是生成调试信息,不加的话 GDB 无法设置断点。$gcc问题匹配器能把 GCC 的编译错误和警告输出解析到 VS Code 的“问题”面板,方便点击跳转。
launch.json的关键参数:
{ "name": "GDB Launch", "type": "cppdbg", "request": "launch", "program": "${fileDirname}/${fileBasenameNoExtension}.exe", "miDebuggerPath": "C:/mingw64/bin/gdb.exe", "cwd": "${fileDirname}", "setupCommands": [ { "text": "-gdb-set disassembly-flavor intel" } ] }setupCommands里的-gdb-set disassembly-flavor intel让反汇编显示为 Intel 语法,比默认的 AT&T 语法更适合大多数人阅读。
5.4 日志输出到文件:排查编译问题的技巧
GCC 12.2.0 编译错误输出默认走 stderr,在 VS Code 终端里太长时会刷掉前面的关键报错。将其重定向到文件是排查问题的基本功:
gcc -Wall -Wextra -Iinclude -c src/main.c -o build/main.o 2> build/error.log把 stderr 单独写入build/error.log,终端只看命令状态,然后打开日志文件搜索error:。如果错误过多,配合-Wfatal-errors让 GCC 在第一个错误后停止编译,避免大量级联误导性的后续错误。这一招在处理 C++ 模板实例化引发的几百条报错时尤其好用。
6. 链接 MSVC 导入库:MinGW 12.2.0 与 MSVC 合作的符号约定
6.1 场景与必要条件
很多项目是 MinGW 编译应用、MSVC 编译链接库,或者反过来。两者合作时最大的障碍是符号命名规则:MSVC 的 C 函数默认使用__cdecl调用约定,导出符号前不加下划线;而 32 位 MinGW 的 C 函数符号带有前导下划线。这个差异在 64 位下反而不明显,因为 x64 只有一种调用约定。
GCC 12.2.0 在 64 位下编译 DLL 时,默认导出的函数名就是不含下划线的裸名,这让它和 MSVC 的链接器配合变得相对顺畅。但反过来,如果 MSVC 编译的.lib导入库要被 MinGW 使用,需要先把.lib转成.a格式,或者用dlltool从 DEF 文件生成.dll.a。
6.2 用一个 DEF 文件搭建跨编译器桥梁
假设 MinGW 编译了一个mylib.dll,想给 MSVC 工程链接。解决办法是利用之前提到导出时生成的.def文件,在 MSVC 的lib.exe环境里执行:
lib /machine:x64 /def:mylib.def /out:mylib.lib这个命令会生成一个 MSVC 风格的导入库mylib.lib,里面只包含导入表,不包含实际代码。MSVC 工程在链接时加上这个文件,并在头文件中声明:
__declspec(dllimport) int add_numbers(int a, int b);__declspec(dllimport)告诉 MSVC 编译器这个函数来自外部 DLL,它会生成经由__imp_指针调用的代码,效率高于直接GetProcAddress。MinGW 编译的 DLL 导出表里函数名没有额外修饰,MSVC 导入后调用约定一致,运行时完全兼容。
6.3 x86 32 位下的符号修饰处理
如果项目不得不留在 32 位,MinGW 和 MSVC 之间的符号修饰差异就需要显式处理。MSVC 默认把__cdecl函数导出为_funcname,而 MinGW 的导出表里可能是funcname,于是 MSVC 链接时找不到符号。
此时可以在 MSVC 头文件里加上:
#pragma comment(linker, "/EXPORT:funcname=_funcname")或者在 MinGW 侧编译时使用-Wl,--kill-at参数,它会让生成的 DLL 导出函数名去掉结尾的@N修饰。但要注意,--kill-at有可能导致 32 位下 stdcall 调用方产生不匹配,使用前必须确认目标函数的调用约定。
6.4 实用建议
在实际工程里,我会尽量避免让 MinGW 直接链接 MSVC 生成的.lib文件,反过来也一样。最稳妥的互操作方式是约定一个 C 接口层:两边都只通过纯 C 函数通信,数据结构只用固定宽度整数和普通指针,编译时各自生成 DLL 和导入库,运行时二进制兼容性由 Windows 加载器保证。这种方案避开了 CRT 冲突、异常处理模型差异和operator new分配器不一致的问题,省下来的排查时间远比写接口层的时间多。
如果你手上正好是 MinGW-w64 GCC 12.2.0 和 MSVC 混用的项目,建议先在最小例子上用dumpbin /exports和objdump -p分别看两边的导出表,确认符号名字一致后再大规模联调。我用这个顺序处理过不下三个项目,没有一次是因为最终代码逻辑出问题,全都是符号约定不一致导致的。希望帮到你。
本文还有配套的精品资源,点击获取