简介:这是一份面向 Windows 64 位平台 C、C++ 与 Fortran 开发者的 MingW64 免安装压缩包,主要解决安装流程繁琐、环境搭建耗时的问题,适合刚接触 GCC 工具链的初学者以及需要快速恢复开发环境的日常开发者,尤其适合便携使用、在多台机器间快速部署工具链的场景。包内共 3125 个文件,以 .a 静态库和 .h、.hpp 头文件为主体,辅以 .exe 命令行程序、.dll 动态库、.lib 导入库、.tcc 模板文件及日志说明文档,覆盖编译器、链接器与运行库等核心组件;压缩包约 48.14MB,解压后即可得到较完整的 64 位 GNU 开发环境。目前已有 10382 人学习下载,受关注度较高。对于希望在 Windows 上使用 gcc、g++、gfortran 进行命令行编译、构建原生应用,或配合 Git Bash 等类 Linux 工具链开展工作的读者,这份包提供了可直接使用的工具程序、头文件与库资源,便于完成课程实验、小型项目练习和跨平台代码验证。
1. 解压即用的 mingw64:我为什么把它塞进 U 盘而不是装安装包
上周帮一个做嵌入式的朋友在工控机上编译一个小工具,机器是内网环境,装不了在线安装器,管理员权限还要走审批。我从 U 盘里掏出一个压缩包,解到D:\tools\mingw64,改了两行 PATH,gcc -v直接吐出 target 信息——前后不到三分钟。mingw64 这类便携工具链最大的价值就在这儿:它是一份自包含的 GCC for Windows,bin 目录里躺着 gcc、g++、gdb、mingw32-make、windres、objdump,解压出来就是可用状态,不写注册表、不装运行库、不动系统目录。适合三类人:需要多版本编译器并存的开发者、在受限机器上做编译验证的工程师、以及想给同学或同事发一份"拿到就能跑"环境的人。代价是它不会帮你管 PATH,也不会自动升级,所有边界都得自己兜住。
2. 拆开压缩包看目录:bin、lib、include 与 x86_64-w64-mingw32 的分工
先看清结构再动手,比出问题后满世界搜报错高效得多。我经手的每一个便携版,解压后根目录大致都是这几层,缺一层基本就是解压没完成或者被安全软件拦了。
| 目录 | 主要内容和作用 |
|---|---|
bin/ | 面向用户的入口:gcc.exe、g++.exe、gdb.exe、mingw32-make.exe、windres.exe、ar.exe、objdump.exe、ld.exe |
include/ | C 标准库头文件,stdio.h、stdlib.h这些 |
lib/ | 链接期用的静态库与导入库,libgcc.a、libstdc++.a、libmingw32.a |
libexec/gcc/<target>/<ver>/ | 编译器内部程序,cc1.exe、cc1plus.exe、collect2.exe,报 "cannot execute cc1" 就是这里出问题 |
x86_64-w64-mingw32/ | 真正的 Windows 目标平台目录,下面还有include/(Windows API 头)、lib/(user32.a、kernel32.a导入库)、bin/(运行期 DLL) |
share/ | 文档、locale、部分 license 文件 |
最容易搞混的是两个include和两个lib。根目录那套是编译器自身的运行库视角,x86_64-w64-mingw32/那套才是目标平台视角。当你写#include <windows.h>时,gcc 去的是后者;当你-luser32时,找的也是后者lib/下的libuser32.a。这个分层不是冗余,是为了让同一份 GCC 能交叉编译到不同 target。
2.1 解压路径与 PATH 的正确加法
第一条血泪经验:不要解压到C:\Program Files\下面,也不要用中文目录名。不是因为 mingw64 本身不支持,而是你后面接的 make、CMake、编辑器插件、调试器脚本里,总有某个环节在引号转义上翻车。我一般固定在D:\tools\mingw64或C:\dev\mingw64,全英文、无空格、层级短。
解压工具也有讲究。用支持保留目录层级的工具解,别用某些"解压到当前文件夹"的快捷操作把 bin 里的文件摊平。摊平之后gcc.exe会找不到同目录的cc1.exe和 libexec 下的东西,症状是gcc: error: cannot execute 'cc1': No such file or directory,看起来像编译器坏了,其实是路径结构毁了。
PATH 只加bin,不要加根目录。会话级的临时设置最安全,出问题关掉终端就恢复:
:: cmd 里临时设置,只影响当前窗口 set "PATH=D:\tools\mingw64\bin;%PATH%" where gcc# PowerShell 里等价写法 $env:Path = "D:\tools\mingw64\bin;" + $env:Path Get-Command gcc | Select-Object Source逻辑说明:把bin前置而不是追加,是为了让这份 mingw64 优先于系统里可能存在的其他编译器。where gcc(PowerShell 用Get-Command gcc)会按 PATH 顺序列出所有命中项,第一条就是实际生效的那个。参数上没有任何玄学,唯一要记住的是:PATH 里出现两个 gcc,你的编译器和调试器就可能来自不同版本,后面第 5 章会专门讲这个坑。
确认长期可用再写进用户环境变量。图形界面里改完必须重开终端,已打开的窗口不会刷新环境块。另一个常被忽略的点是 PATH 总长度,Windows 用户级加系统级有上限,塞太多工具链进去会截断,表现是某些目录莫名其妙不生效。工具链多的机器,我更推荐用批处理按项目切换,而不是全塞全局 PATH。
2.2 用 gcc -v 和 -dumpmachine 做一次自检
PATH 配好之后别急着写代码,先跑三条自检命令,把工具链的底细摸清楚:
gcc -dumpmachine gcc -v gcc -print-search-dirs-dumpmachine输出的是 target triple,常见结果是x86_64-w64-mingw32,这一行直接告诉你这份包是 64 位目标。如果你拿到的是i686-w64-mingw32,那就是 32 位目标工具链,编译出来的 exe 默认也是 32 位,跟 64 位库混链会报file format not recognized。
-v的输出信息量最大,重点看三处:Target:确认目标平台;Thread model:是posix还是win32,这决定你能不能直接用std::thread和<thread>,win32模型下用 C++11 线程会直接链接失败;Configured with:里能看到--enable-threads、--enable-seh之类的开关,异常模型是 SEH 还是 SJLJ 也在这里体现。
-print-search-dirs打印install:、programs:、libraries:三组搜索路径。当链接报cannot find -lxxx时,把这里的libraries:列表和你要链接的库对一遍,能立刻判断是库不存在还是路径没进去。
提示:这三条命令的输出请原样留存一份。换机器、换版本、给别人排查时,对比这份基线比凭记忆猜快得多。
自检通过的标准很简单:gcc -dumpmachine有输出、gcc -v里 target 和 thread model 符合预期、gcc -print-search-dirs的 install 路径指向你解压的那个目录而不是别处。三条都过,才进入下一步。
3. 从单文件到工程:gcc 命令行与 mingw32-make 的两套打法
便携版 mingw64 有个很现实的取舍:它通常不带完整的 POSIX shell 环境,所以你拿到的mingw32-make默认在cmd.exe下执行命令。这一点直接决定了 Makefile 怎么写,也是从 Linux 迁过来的同学最容易翻车的地方。这一章先把单文件编译跑通,再上多文件工程。
3.1 单文件编译与 -static 的必要性
先写一个最小的hello.c:
#include <stdio.h> int main(void) { printf("hello from mingw64\n"); return 0; }编译命令按用途分三档:
# 1) 本机自用:动态链接,产物小,但依赖同目录或 PATH 里的 DLL gcc -O2 -Wall -Wextra -o hello.exe hello.c # 2) 要发给别人:全静态,产物大但拷过去就能跑 gcc -O2 -Wall -Wextra -static -o hello_static.exe hello.c # 3) C++ 项目:显式静态链接运行库,兼顾体积和可分发性 g++ -O2 -Wall -Wextra -static-libgcc -static-libstdc++ -o app.exe app.cpp参数说明:-O2开常规优化,调试阶段可以换-O0 -g;-Wall -Wextra打开告警,mingw64 对格式串、隐式声明的告警比某些商业编译器严格,早开早省事;-static把 libgcc、libstdc++、winpthread 全部静态进来,代价是 exe 体积从几十 KB 涨到一两 MB;-static-libgcc -static-libstdc++是折中方案,只静态运行库,系统 DLL 仍然动态。
这三档的差别,只有在把 exe 拷到另一台没装工具链的机器上才会原形毕露。动态编译的 C++ 程序换机后大概率弹窗提示缺libstdc++-6.dll或libgcc_s_seh-1.dll。分发场景我一律用-static,省下来的解释成本远高于那点体积。
还有一个容易被忽略的开关是-mwindows。加上它,链接器把子系统设成 GUI,程序启动不弹控制台窗口;不加就是控制台程序。做带界面的小工具时忘了加,用户会看到一个黑框跟着窗口一起出来。
3.2 用 Makefile 管多文件:路径分隔符与通配符
多文件工程上手写编译命令很快就不可维护,mingw32-make是包里自带的答案。下面这份 Makefile 是我在便携环境下反复用过的骨架:
# 工具链 CC := gcc CXX := g++ # 编译选项 CFLAGS := -O2 -Wall -Wextra -std=c11 LDFLAGS := -static # 目标与源文件 TARGET := app.exe SRCS := $(wildcard src/*.c) OBJS := $(patsubst src/%.c,build/%.o,$(SRCS)) $(TARGET): $(OBJS) $(CC) $(OBJS) -o $@ $(LDFLAGS) # 注意:命令行前面是 Tab,不是空格 build/%.o: src/%.c @if not exist build mkdir build $(CC) $(CFLAGS) -c $< -o $@ clean: @if exist build rmdir /s /q build逻辑说明:wildcard展开src/*.c拿到源文件列表,patsubst把src/x.c映射成build/x.o,这样源文件和中间产物分离,clean一条命令就能清干净。$@是目标名,$<是第一个依赖,属于 make 的自动变量,不用死记,写两遍就顺了。
真正的坑在命令行的 shell 上。这份 Makefile 里用的是if not exist build mkdir build,因为mingw32-make在 Windows 上默认走cmd.exe,你写mkdir -p build它会老老实实创建一个名叫-p的目录,或者直接报错。想用 Unix 风格命令,得在 Makefile 顶部显式指定:
SHELL := cmd.exe # 或者,如果你手里有 sh.exe(部分便携包附带) # SHELL := sh.exe我的习惯是:便携环境下不折腾 SHELL,直接用 cmd 语法写目录创建和清理,可控性最高。如果你确实需要rm -rf、&&串联这类写法,确认包里有sh.exe再切,否则会出现"在我机器上能编,换台机器就报命令不存在"的经典问题。
另一个细节是路径分隔符。Makefile 里统一用正斜杠/,gcc 和 make 都能接受,反斜杠在 make 里是转义符,写了反而容易出事。只在if exist这类 cmd 内建命令里用反斜杠。
通配符在含空格路径下会炸。$(wildcard src/*.c)遇到src/my code/a.c会被拆成两个词元,报No rule to make target。所以回到第 2 章那句话:工程根目录别带空格,这条约束能省掉后面无数麻烦。
4. 链接 Windows 系统库:user32、windres 资源与 DLL 依赖链
GCC 在 Windows 上编译出来的是原生 PE 程序,不是模拟层。这意味着你可以直接调 Win32 API,也意味着链接阶段的库名、子系统、资源文件都得按 Windows 的规矩来。这部分在 Linux 上根本遇不到,是便携版 mingw64 用户最需要补齐的一块。
4.1 链接 user32、gdi32 这类系统库
写一个最小的消息框程序:
#define WIN32_LEAN_AND_MEAN /* 砍掉 winsock1 等一堆不常用的头,加快编译 */ #include <windows.h> int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmd, int show) { MessageBoxA(NULL, "hello", "mingw64", MB_OK | MB_ICONINFORMATION); return 0; }编译命令:
gcc -O2 -Wall -o msgbox.exe msgbox.c -luser32 -mwindows参数说明:-luser32告诉链接器去x86_64-w64-mingw32/lib/下找libuser32.a,这是导入库,不含实现,只负责把调用转发到系统 DLL;-mwindows设定 GUI 子系统,程序不会附带控制台。顺序上-l要放在源文件之后,GCC 的链接顺序是从左到右解析未定义符号,库写在前面会导致符号找不到。
需要多个系统库时按依赖顺序排列,常见的组合是-luser32 -lgdi32 -lcomctl32 -lwinmm -lole32。报undefined reference to __imp_XXX时,第一反应应该是查这个符号属于哪个 DLL,再补对应的-l,而不是去搜"mingw 链接错误怎么解决"。
带资源的程序还要多一步。.rc文件描述图标、版本信息、对话框模板,用windres编译成 COFF 对象再参与链接:
# 把资源脚本编译成对象文件 windres app.rc -O coff -o app.res # 一起链接进可执行文件 gcc -O2 -o app.exe app.c app.res -luser32 -mwindows-O coff指定输出格式,Windows 目标必须是 coff,写成别的格式后续链接会报无法识别。.rc里引用图标时路径用相对路径,别用绝对路径,否则换台机器就找不到文件。
4.2 用 objdump 查 DLL 依赖,别等用户报错
程序编出来能跑,不代表拷到别人机器上能跑。分发前我最少做一次依赖检查:
# 列出 PE 导入表里的 DLL 名称 objdump -p app.exe | findstr /i "DLL Name" # 查看链接器实际搜索和引入的库(调试链接问题时用) gcc -O2 -o app.exe app.c -Wl,--trace 2>&1 | findstr /i "user32"objdump -p输出的是 PE 结构里的导入目录,能看到KERNEL32.dll、msvcrt.dll、USER32.dll这类系统库,也能看到libstdc++-6.dll、libwinpthread-1.dll这类工具链运行库。前者在 Windows 上必然存在,后者才是分发风险点。-Wl,--trace会把链接器打开过的每个库打印出来,排查"到底链上了哪一份"时特别有用。
Windows 的 DLL 搜索顺序和 Linux 完全不同:先是 exe 所在目录,然后系统目录,再是 PATH。也就是说,你把libstdc++-6.dll和 exe 放同一个文件夹,它一定能找到;但如果你只把它加到 PATH 靠后的位置,而系统目录里有个旧版本,那就会静默加载旧版本,症状是程序启动即崩或者行为诡异。这个"就近优先"的规则既是便利也是陷阱,第 5 章会展开讲。
注意:
objdump -p只能看直接依赖,看不到运行期LoadLibrary动态加载的 DLL。如果你的代码里有显式加载逻辑,得单独确认目标 DLL 的存在性和位数。
4.3 静态链接与运行库的三条路
C++ 项目的依赖问题比 C 复杂一档,因为它多出libstdc++和libwinpthread两条链。三条路可选:
| 方案 | 编译参数 | 优点 | 代价 |
|---|---|---|---|
| 全静态 | -static | 单文件分发,零依赖 | 体积大,某些场景下 CRT 行为与系统 DLL 版略有差异 |
| 半静态 | -static-libgcc -static-libstdc++ | 体积适中,只留系统 DLL | libwinpthread-1.dll可能仍需要一起带 |
| 全动态 | 默认 | 产物最小 | 换机必缺 DLL,只适合本机调试 |
我的默认选择是半静态加显式处理 winpthread:用了<thread>、<mutex>且工具链是 posix 线程模型时,链接参数补上-Wl,-Bstatic -lwinpthread -Wl,-Bdynamic,把这条链也吃掉。这样最终 exe 只依赖系统 DLL,拷到任何一台 Windows 上都能跑。
需要提醒的是,全静态在涉及getaddrinfo、区域设置、部分 C11 原子操作时,行为可能和动态版有细微差别。遇到"本机正常、分发后异常"且排查不到业务代码的,先换回半静态对照一次,能快速定位是不是静态链接引入的差异。
5. 避坑清单:解压版 mingw64 最容易翻车的五处
便携包的好处是干净,坏处也是干净——没有安装器帮你处理环境,所有状态都得自己维护。下面五条是我在不同机器上真实踩过的,按出现频率排序。
5.1 换机就提示缺 libstdc++-6.dll
现象:本机编译运行正常,把 exe 发给同事,双击弹窗提示找不到libstdc++-6.dll或libgcc_s_seh-1.dll。
原因:默认编译是动态链接工具链运行库,这些 DLL 只存在于你解压目录的x86_64-w64-mingw32/bin/下,没进系统目录,也没跟 exe 放一起。
解决:重新用-static编译;或者把x86_64-w64-mingw32/bin/下的三个运行库 DLL 复制到 exe 同级目录。前者更省事,后者适合必须动态链接的场合。别去改系统 PATH 解决这个问题,那只是把依赖藏起来了。
5.2 PATH 里有两个 gcc,编译和调试对不上
现象:编译通过了,gdb 调试时断点打不上,或者报No symbol table is loaded,甚至栈回溯全是问号。
原因:PATH 里还有另一份编译器(某个 IDE 自带的、之前装过的),gcc命中的是 A 版本,gdb命中的是 B 版本。两者对调试信息的格式理解不一致,符号表读不出来。
解决:where gcc、where gdb、where mingw32-make三条一起看,确认它们全部指向同一个bin目录。只要有一条跑到别处,就把那份 mingw64 的bin前置到 PATH 最前面,或者干脆在项目脚本里写绝对路径。
:: 一次性确认三个关键程序来源一致 where gcc & where gdb & where mingw32-make5.3 路径带空格,make 报 CreateProcess failed
现象:mingw32-make执行到某条命令时报process_begin: CreateProcess failed,或者提示找不到某个中间文件。
原因:工程放在C:\Users\某某\My Projects\这类带空格或中文的路径下,make 在拼命令行时没有正确加引号,参数被空格截断。
解决:工程目录改成全英文无空格路径,这是成本最低的做法。实在改不了,在 Makefile 里给路径变量手工加引号,并确认每条引用它的命令都带引号,工作量和出错概率都不低。
5.4 杀毒软件静默隔离了 bin 下的 exe
现象:昨天还能编,今天突然报gcc: error: cannot execute 'cc1': No such file or directory,但bin目录看起来完好。
原因:cc1.exe、cc1plus.exe这类编译器内部程序行为特征明显(大量文件读写、子进程调用),容易被安全软件当成可疑对象处理掉,而且往往不弹窗只记日志。
解决:去安全软件的隔离区确认并恢复,然后把整个 mingw64 目录加入白名单。这类工具链目录加白名单是常规操作,不是"关掉防护",别搞混。
5.5 控制台中文输出乱码
现象:源文件里写了中文字符串,编译通过,运行时控制台显示乱码或者问号。
原因:源文件保存为 UTF-8,而 Windows 控制台默认代码页是 GBK(936),程序把 UTF-8 字节直接写出去,终端按 GBK 解释,自然对不上。
解决:编译时指定执行字符集,让字符串在编译期转成 GBK:
gcc -O2 -finput-charset=UTF-8 -fexec-charset=GBK -o app.exe app.c-finput-charset告诉编译器源文件是什么编码,-fexec-charset指定字符串字面量落进二进制时的编码。如果程序需要在不同区域设置的机器上都正确显示,更彻底的做法是源码里用宽字符,运行时调SetConsoleOutputCP(CP_UTF8)切换控制台代码页,而不是靠编译期转码。这类乱码问题没有通用解,先确定源文件编码和目标终端代码页,再决定走哪条路。
6. 接进 CMake 与编辑器:一次配置跑通编译和 gdb 调试
手写 Makefile 适合小工程,一旦依赖第三方库,CMake 的收益就出来了。便携版 mingw64 接 CMake 的关键只有一条:显式指定生成器和编译器路径,别指望 CMake 自己猜。它猜错时的表现很迷惑——可能生成了一套 Visual Studio 工程,也可能报找不到编译器但不说找的是哪个。
cmake_minimum_required(VERSION 3.20) project(demo C CXX) # 强制使用我们解压出来的工具链,避免命中系统里的其他编译器 set(CMAKE_C_COMPILER "D:/tools/mingw64/bin/gcc.exe") set(CMAKE_CXX_COMPILER "D:/tools/mingw64/bin/g++.exe") set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_executable(demo src/main.cpp) target_compile_options(demo PRIVATE -Wall -Wextra) target_link_options(demo PRIVATE -static) target_link_libraries(demo PRIVATE user32)配置和构建命令固定成两步走,生成器必须显式给成MinGW Makefiles:
# 在工程根目录执行,build 目录独立出来 cmake -S . -B build -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release cmake --build build -j 8参数说明:-G "MinGW Makefiles"指定用 mingw32-make 驱动,不带这个参数 CMake 在 Windows 上默认挑 Visual Studio 生成器;-DCMAKE_BUILD_TYPE=Release开启优化,调试时换成Debug并确保带了-g;-j 8并行编译,数字按核数给。第一次配置建议把-DCMAKE_C_COMPILER也放在命令行上传一次,确认输出里的 "The C compiler identification is GNU" 后面跟的路径就是你解压的那份。
编辑器这边,我一般让配置文件全部落在项目.vscode/下,换机器不用重配:
{ "version": "2.0.0", "tasks": [ { "label": "build", "type": "shell", "command": "cmake", "args": ["--build", "build", "-j", "8"], "group": { "kind": "build", "isDefault": true }, "problemMatcher": ["$gcc"] } ] }problemMatcher用$gcc而不是$msCompile,否则编译错误的行列号解析不出来,点击跳转会失效。调试配置里把miDebuggerPath指向同一个bin下的gdb.exe,program指向build/demo.exe,setupCommands里加一条-enable-pretty-printing方便看 STL 容器。想更省事的话,在项目根放一个CMakePresets.json,把生成器、构建类型、二进制目录全写进去,命令行和编辑器共用同一份配置,就不用维护两套参数了。
提示:
c_cpp_properties.json里的compilerPath建议同时填上,它决定 IntelliSense 用哪套头文件。填错的表现是代码能编译但编辑器里满屏红波浪线。
这里有个教训值得说:早期我图省事,把工具链路径写进全局环境变量,项目里一律不写绝对路径。结果在一台装过三个编译器的机器上折腾了整整一下午,编译器、调试器、CMake 各认各的版本,报错信息互相矛盾。从那以后我每个工程都强制走一遍"编译器绝对路径 + 生成器显式声明 +where三连自检",配置看着啰嗦,但换机器、换人都能一次跑通。希望帮到你。
本文还有配套的精品资源,点击获取