简介:MingW GCC 12.2.0 面向需要在 Windows 64 位平台上开发 C/C++ 的编程者与学习者,提供一套不依赖 Visual Studio 的开源编译工具链,可用于现代 C++ 特性练习、跨平台项目构建以及配合 CMake 管理工程。压缩包共 2000 个文件,约 68.08MB,以 html 文档、h/hpp 头文件、a/lib 静态库、exe/dll 可执行与动态库文件为主,并含 py/pyc 脚本、tcl/msg 配置等辅助内容,覆盖编译、链接、调试与终端配置等环节。已有 394 人学习下载。读者可从中获得 GCC 12.2.0 的命令行编译环境、标准库与头文件、GDB 调试入口及 CMake 相关组件,便于快速搭建本地工具链、验证 C++20 等新特性,并减少自行拼装依赖与排查环境问题的时间。
1. 从一条gcc -v的输出说起
某开发者把同事发来的一段std::ranges代码拷到自己机器上,g++一跑,满屏error: 'ranges' is not a member of 'std'。代码本身没问题,问题出在 IDE 自带的那个 GCC 8.1.0 上。在 Windows 上装 MinGW GCC 12.2.0,麻烦的地方在于装完之后,究竟哪一套工具链真正被系统用上,而非下载本身。机器上原本就可能有 IDE 自带的工具链、别的开发环境顺带装上的编译器,甚至上次没装干净遗留下来的残包;PATH 一长,谁先被命中,谁就生效。12.2.0 不算新版本,但足够稳:C++17 的支持已经完整,C++20 的 concepts、ranges、协程也基本齐了,比老项目里锁着的 8.x、9.x 明显好用,又不像更新的版本那样容易撞上第三方库还没跟上。下面按我自己的落地顺序写:先选包和运行时模型,再解压定型、配 PATH,然后拿最小工程验证,最后是五个最常见的翻车现场和救法。
2. 装之前先把版本选对:MinGW-w64 GCC 12.2.0 的发行包与运行时模型
2.1 MinGW、MinGW-w64 与 GCC 12.2.0 到底谁是谁
很多人把这三者当同一个东西,装的时候就容易下错包。GCC 本身是编译器,包含cc1(C 前端)、cc1plus(C++ 前端)和后端代码生成;MinGW 最早是把 GCC 移植到 Windows 上的一套补丁和运行时;MinGW-w64 则是从 MinGW 分出来的一条独立分支,补上了 64 位支持和更完整的 Windows API 头文件。所以当你看到“mingw gcc 12.2.0”这个说法时,它指的其实是一整套发行包,而不是单独一个编译器可执行文件。
一份能直接干活的 MinGW-w64 GCC 12.2.0 发行包里,至少包含下面这几块东西:
| 组成 | 关键文件 | 作用 |
|---|---|---|
| 编译器驱动 | bin\gcc.exe、bin\g++.exe | 你敲的命令,负责调度后端 |
| 编译器后端 | libexec\gcc\x86_64-w64-mingw32\12.2.0\cc1.exe等 | 真正做语法分析和代码生成 |
| 二进制工具 | bin\as.exe、bin\ld.exe、bin\objdump.exe | 汇编、链接、查产物 |
| CRT 与 Windows 头文件 | x86_64-w64-mingw32\include\ | windows.h、stdio.h这一层 |
| C++ 标准库 | lib\libstdc++.a、libstdc++-6.dll | std::string、std::vector的来源 |
| 可选工具 | bin\gdb.exe、bin\mingw32-make.exe | 调试和构建 |
理解了这张表,后面排查“找不到头文件”“缺 DLL”这类问题时,就知道该去哪个目录里找线索了。我见过有人只把gcc.exe单独拷到C:\Windows下,结果一编译就报找不到cc1.exe,原因就是只搬了驱动,没搬后端。
2.2 线程模型和异常模型:两个必须选对的开关
同一个 12.2.0 版本号,市面上存在好几种变体,差异就在这两个编译选项上。选错了不会立刻报错,但会在用std::thread或者抛异常的时候给你难堪。
| 维度 | 可选值 | 影响什么 | 我一般怎么选 |
|---|---|---|---|
| 目标架构 | i686/x86_64 | 生成 32 位还是 64 位程序 | 一律x86_64,除非要兼容很老的环境 |
| 线程模型 | win32/posix | std::thread、std::mutex、std::condition_variable能不能用 | 选posix,win32模型下 C++11 线程库基本残废 |
| 异常模型 | seh/sjlj/dwarf | 异常展开方式和开销 | 64 位选seh,它走的是 Windows 原生结构化异常,性能最好 |
win32线程模型是个经典陷阱。它在 32 位时代很常见,因为当年 posix 模型在 Windows 上性能不佳。但到了 GCC 12.2.0 这个年代,如果你拿到的包是x86_64-win32-sjlj这种组合还勉强能编译,只是写多线程程序时std::thread会直接编不过,报thread不是std的成员。所以拿到压缩包的第一件事,是看文件名里有没有posix和seh这两个词。
装完之后也有办法回头确认,gcc -v的输出里会带这几行:
# 在 cmd 里执行,看 Target 和 Thread model 两行 gcc -v输出里Target: x86_64-w64-mingw32说明是 64 位包,Thread model: posix说明线程模型对了。这两行对不上,后面的活基本白干,趁早换个包重来更省时间。
2.3 三条安装路线的取舍
Windows 上搞到 GCC 12.2.0,常见有下面三条路,各有各的适用场景:
| 路线 | 典型形态 | 版本可控性 | 适合谁 |
|---|---|---|---|
| 包管理器安装 | 用 MSYS2 的pacman拉mingw-w64-x86_64-gcc | 跟着仓库滚动,装到的往往不是 12.2.0 | 想要一整套 unix 工具链、不在乎具体小版本的人 |
| 独立压缩包 | 下载对应版本的归档包,解压即用 | 版本锁死,可多版本并存 | 项目要求钉死 12.2.0 的人,也是这篇的主线 |
| IDE 自带 | 装完 IDE 目录里就有mingw64 | 完全不可控 | 只想跑个 Hello World 的人 |
MSYS2 那条路的好处是依赖自动解决,gdb、pkg-config、make一条命令全带上,缺点是它给你的永远是仓库当前版本,想要正好 12.2.0 得去翻归档,操作反而绕。IDE 自带的那套最省事,但它和 IDE 的生命周期绑死,一旦你换编辑器或者要在一台机器上同时维护两个不同 GCC 版本的项目,就会很难受。
独立压缩包这条路的成本主要在两个地方:一是要自己配 PATH,二是发行包不一定带gdb。但换来的是路径完全可控、多版本可以并排放、删掉就是删掉不留残余。对需要复现构建环境的场景来说,这笔账很划算。
注意:这三条路不是互斥的。我自己的机器上就同时留着 MSYS2 和一份独立的 12.2.0 压缩包,日常敲命令用 MSYS2,做版本验证时切到独立目录。
2.4 落地前的目录规划和前置准备
目录这件事值得单独说,因为它决定了后面一半的坑会不会出现。三条硬性要求:
第一,路径里不能有空格和中文。C:\Program Files\mingw64这种路径,在某些构建脚本和 Makefile 里会被空格切断,报出来的错还特别隐晦,比如CreateProcess: No such file or directory,看起来像文件丢了,其实是路径被劈成两半。我一般用D:\dev\mingw64-12.2.0这种形式,短、纯英文、带版本号方便并列。
第二,不要放在需要管理员权限才能写的目录里。Program Files下的东西,普通用户对它是只读的,有些库在编译时会往工具链目录里写缓存,权限不够就失败。
第三,解压工具用 7-Zip 或者系统自带的解压都行,但别用那些会自动“修正”目录结构的解压软件。MinGW 的发行包对目录层级很敏感,bin、lib、include、libexec必须在同一层,被套了一层额外目录就会全线报错。
准备工作还有一项:如果机器上已经有一套旧的 MinGW 在 PATH 里,先记下来它指向哪儿。Windows 的 PATH 查找是从前到后,后加的路径排在末尾,如果旧路径在前面,你新装的 12.2.0 根本轮不到出场。这个后面配 PATH 的时候要用到。
3. 正式安装:把 GCC 12.2.0 落到磁盘并接进 PATH
3.1 解压定型与目录结构核对
拿到 12.2.0 的压缩包后,先解压到一个临时目录看一眼结构,别急着往最终位置放。正常的顶层应该是这样:
# 用 cmd 看解压后的第一层结构 dir /b D:\dev\mingw64-12.2.0期望看到的条目大致是bin、etc、include、lib、libexec、licenses、share、x86_64-w64-mingw32这些。如果多套了一层同名目录,比如mingw64-12.2.0\mingw64\bin,那就把里层整个挪上来,别嫌麻烦,这一步偷懒后面要还。
目录定型之后,用一条命令确认关键可执行文件都在:
# PowerShell 里列出 bin 目录下的核心工具,确认四大件齐全 Get-ChildItem 'D:\dev\mingw64-12.2.0\bin' -Filter *.exe | Where-Object { $_.Name -in @('gcc.exe','g++.exe','gdb.exe','mingw32-make.exe','objdump.exe') } | Select-Object Name, Length这条命令的过滤条件就是四个核心文件加一个排查用的objdump.exe。Length字段顺手能看到文件大小,如果某个文件是 0 字节或者只有几 KB,说明解压不完整,得重新解。gdb.exe和mingw32-make.exe缺失不算致命,前者影响调试,后者影响用 Makefile 构建,可以单独补。
再往下确认编译器后端有没有到位,这是最容易漏的一环:
# 后端 cc1/cc1plus 不在 libexec 下,gcc 一编译就会报错 dir /b "D:\dev\mingw64-12.2.0\libexec\gcc\x86_64-w64-mingw32\12.2.0"这里应该能看到cc1.exe、cc1plus.exe、collect2.exe之类。这个路径里的12.2.0就是编译器版本号,它同时也是判断你手上这套包到底是不是 12.2.0 的硬证据——文件名可以改,这个目录名一般不会。
3.2 配置 PATH:用户变量还是系统变量
到了这一步,工具链在磁盘上躺好了,但系统还不知道它。PATH 的配法有两种,我强烈建议只动用户变量。
系统变量改的是全机器共享的那份,需要管理员权限,而且一旦写坏,影响的是这台机器上所有账户。用户变量只影响你自己,出问题删掉重来就行,风险小得多。除非这台机器是给某个服务账户跑构建用的,否则没必要碰系统变量。
图形界面的操作路径是:Win + R输入sysdm.cpl,进“高级”标签页,点“环境变量”,在上半部分的用户变量里选中Path,点“编辑”,再点“新建”,把D:\dev\mingw64-12.2.0\bin粘进去,一路确定。注意是到bin这一层,不是到mingw64-12.2.0。
命令行方式更稳,也方便写进部署脚本:
# 安全追加:先读旧值,判断是否已存在,再拼回去,避免重复和截断 $bin = 'D:\dev\mingw64-12.2.0\bin' $old = [Environment]::GetEnvironmentVariable('Path', 'User') if (($old -split ';') -notcontains $bin) { $new = $old.TrimEnd(';') + ';' + $bin [Environment]::SetEnvironmentVariable('Path', $new, 'User') Write-Host "已追加: $bin" } else { Write-Host "已存在,跳过" }几个参数值得说清楚。GetEnvironmentVariable和SetEnvironmentVariable的第二个参数'User'表示操作的是用户级变量,换成'Machine'就是系统级,别写错。-split ';'把 PATH 拆成数组再判断,比用-like "*$bin*"更准,后者在路径互为前缀时会误判。TrimEnd(';')是防止原值结尾本来就带分号,拼完变成;;,虽然多数情况无害,但有些老工具会因此解析出错。
注意:不要用
setx PATH "%PATH%;D:\dev\mingw64-12.2.0\bin"这种写法。setx有 1024 字符的长度限制,PATH 一长就会被静默截断,而且它会把当前会话里的系统 PATH 和用户 PATH 合并后一起写进用户变量,等于把系统路径复制了一份,以后维护会非常乱。
3.3 刷新会话与多套工具链的优先级
PATH 改完之后,已经开着的终端窗口是不会自动感知的,因为环境变量在进程启动时就固化了。这时候最稳的做法是把所有 cmd、PowerShell、编辑器的终端面板全部关掉重开。嫌麻烦的话,PowerShell 里可以手动重载一次:
# 重新拼装当前会话的 PATH,不用关窗口 $env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [Environment]::GetEnvironmentVariable('Path','User')重开之后立刻验证命中的是哪一份:
# cmd 里用 where,它会按 PATH 顺序把所有 gcc 列出来 where gcc where g++如果只输出一行,而且指向D:\dev\mingw64-12.2.0\bin\gcc.exe,那就干净了。如果输出两行以上,第一行指向别的目录,说明旧的工具链在前面挡着。这时候有两个选择:一是把新路径在用户变量里往上移到旧路径之前,二是干脆把旧的那条从 PATH 里删掉。我一般选后者,因为留着两套 MinGW 迟早会出“头文件来自 A、库来自 B”的串味问题,那类错误的排查成本极高。
PowerShell 用户注意一点:where在 PowerShell 里是Where-Object的别名,直接敲会报参数错误。要么写成where.exe gcc,要么用Get-Command:
# PowerShell 下查命令实际路径的正确姿势 Get-Command gcc | Select-Object -ExpandProperty Source这一步做完,安装的“物理部分”就算结束了。剩下的都是让这套工具链在新环境里跑通的问题。
4. 让这套 12.2.0 真正能干活:编译参数、静态链接与构建工具对接
4.1 第一个程序:C 和 C++ 分开验证
装完先别急着上真实项目,用两个最小程序把 C 和 C++ 两条链路分别走一遍。先看 C:
/* hello.c —— 顺便把编译器的主版本号打出来,比 --version 更贴近实际 */ #include <stdio.h> int main(void) { printf("built by gcc %d.%d.%d\n", __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); return 0; }# 编译并运行,确认 C 前端与链接器都正常 gcc hello.c -o hello.exe .\hello.exe用__GNUC__这几个预定义宏而不是gcc --version,是因为前者是编译期真实使用的那个编译器写进去的,后者有可能被某个包装脚本或者 IDE 的 shim 劫持,显示的和实际用的不是一回事。输出built by gcc 12.2.0才算真的对上了。
再看 C++,这里要顺带验证标准库:
// hello.cpp —— 用到 vector、sort 和 iostream,把标准库链路走通 #include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> v{3, 1, 4, 1, 5, 9, 2, 6}; std::sort(v.begin(), v.end()); for (int x : v) std::cout << x << ' '; std::cout << '\n'; return 0; }# 注意用 g++ 而不是 gcc,并把 GCC 运行库静态链进去 g++ -std=c++17 -O2 hello.cpp -o hello_cpp.exe -static-libgcc -static-libstdc++ .\hello_cpp.exe为什么要加-static-libgcc -static-libstdc++?因为 MinGW 的g++默认把libstdc++-6.dll、libgcc_s_seh-1.dll、libwinpthread-1.dll这几个做成动态依赖,本机跑没问题,一旦把 exe 拷给别人或者放进 CI 的干净容器里,就会弹“找不到 libstdc++-6.dll”。这两个开关只是把 GCC 自己的运行库静态化,C 运行时会走系统自带的msvcrt.dll,产物不会太大。真要彻底自包含,用-static全静态,代价是体积明显上涨。
4.2 参数取舍:-std、优化和静态链接三个开关怎么定
真实项目里的编译命令比 Hello World 长得多,下面这条是我在一个跨平台小工程里常用的形态:
g++ -std=c++20 -Wall -Wextra -O2 -DNDEBUG ^ -I./include -L./lib -lmylib ^ -static-libgcc -static-libstdc++ ^ -o app.exe src/main.cpp src/util.cpp^是 cmd 的续行符,PowerShell 里换成反引号`,bash 里是反斜杠。逐个说参数背后的取舍:
-std=c++20在 GCC 12.2.0 上是安全的,concepts、ranges、协程、三路比较这些都能用。但有一个例外要记牢:<format>和std::format这个版本还没有,从 GCC 13 才开始提供。有人在 12.2.0 上写#include <format>然后满世界找原因,其实换个版本或者临时用第三方格式化库就行。
-Wall -Wextra建议常开。GCC 12 的警告比 8.x 严不少,-Wmaybe-uninitialized、-Wstringop-overflow这类现在会更积极地报出来,老代码升级上来经常一片红。别急着用-w关掉,那些警告里有一部分确实对应真问题。
-O2是常规优化。-O3会开向量化和更激进的函数内联,编译时间翻倍,产物还可能变大,除非是计算密集型代码,否则没必要。调试期把-O2换成-g -O0,出问题的时候栈帧才对得上。
-DNDEBUG关掉assert,只应该在 Release 构建里加。开发期忘了去掉,会出现“本地断言不生效”的诡异情况。
-I./include -L./lib -lmylib这三个是给第三方库用的,-l后面的名字要去掉lib前缀和.a/.dll后缀,比如libmylib.a写成-lmylib。链接器搜索顺序是先-L指定的目录,再系统默认路径,顺序写反会导致链到旧版本。
4.3 让 VS Code 和 CMake 指向这套工具链
命令行能跑通不代表编辑器里能跑通。VS Code 的 C/C++ 扩展默认会自己去猜编译器,猜错的概率不低,尤其是在有多个 MinGW 的机器上。直接把它钉死:
{ "version": 4, "configurations": [ { "name": "MinGW GCC 12.2.0", "compilerPath": "D:/dev/mingw64-12.2.0/bin/g++.exe", "intelliSenseMode": "windows-gcc-x64", "cStandard": "c17", "cppStandard": "c++17", "includePath": ["${workspaceFolder}/**"] } ] }compilerPath这里用正斜杠,是因为 JSON 字符串里的反斜杠是转义符,写D:\\dev\\...也能用,但正斜杠更不容易出错,Windows 的 API 本身两种都认。intelliSenseMode写windows-gcc-x64对应 64 位,写错成windows-msvc-x64会导致补全时按 MSVC 的语义分析,__attribute__之类的扩展会一片红。cppStandard要和命令行里的-std对齐,否则编辑器认为能用的语法,编译时会报错。
用 CMake 的话,生成器必须显式指定,否则它可能默认挑 Visual Studio:
# 生成器用 MinGW Makefiles,两个编译器路径都显式给出 cmake -G "MinGW Makefiles" ^ -DCMAKE_C_COMPILER=D:/dev/mingw64-12.2.0/bin/gcc.exe ^ -DCMAKE_CXX_COMPILER=D:/dev/mingw64-12.2.0/bin/g++.exe ^ -DCMAKE_BUILD_TYPE=Release .. mingw32-make -j8mingw32-make这个名字容易误导,它名字里带 32,但就是给 64 位工具链用的那个 make,不是什么 32 位专用版本。-j8按 CPU 核心数给,别超过物理核心太多,编译大工程时内存会顶不住。如果cmake报找不到 make,把D:\dev\mingw64-12.2.0\bin加进 PATH 之后重开终端就行。
5. 避坑与排查:GCC 12.2.0 在 Windows 上最常见的五个翻车现场
5.1 现象:'gcc' 不是内部或外部命令
原因多半出在 PATH 上,具体又分三种:改完 PATH 没重开终端,环境变量还是旧的;加路径的时候加到了mingw64-12.2.0这一层而不是bin子目录,系统在那一层找不到gcc.exe;或者把路径加进了系统变量,但当前进程用的是受限令牌,读不到。
解决顺序是先在 cmd 里echo %PATH%看一眼那一条在不在,再用where /r D:\dev\mingw64-12.2.0 gcc.exe确认文件本身在磁盘上,最后才怀疑权限。这三步能覆盖九成情况。剩下那一成是 PATH 里有引号包着的路径,Windows 在拼接时会把引号当字符处理,导致整条失效。
5.2 现象:链接阶段报undefined reference to 'WinMain'
这个错看着吓人,其实原因很朴素:链接器在找程序入口,它没找到main,就退而求其次找WinMain,还是没找到,于是报了这个名字。真正的错在源码里,常见的是main拼成了mian,或者写成了int main(int argc)这种不合法签名。还有一种情况是.cpp文件用gcc而不是g++编译,gcc不会自动链libstdc++,符号对不上。
解决:C++ 源文件一律用g++;如果非要用gcc,末尾补-lstdc++;检查main的拼写和签名。另外检查链接命令里有没有-mwindows,这个开关会把子系统切到 GUI,此时入口函数必须是WinMain而不是main,想跑控制台程序就把它去掉。
5.3 现象:本机能跑,拷给别人就提示缺libstdc++-6.dll
原因很清楚:GCC 的 C++ 运行库默认是动态链接的,你的 exe 依赖libstdc++-6.dll、libgcc_s_seh-1.dll、libwinpthread-1.dll这三个文件,而它们只在装了 MinGW 的机器上存在于 PATH 里。别人的机器上自然没有。
解决有两个层次。轻量做法是编译时加-static-libgcc -static-libstdc++,只静态化 GCC 运行库,系统 DLL 仍然动态,产物增加几 MB。彻底做法是-static全静态,连libwinpthread一起链进去,产物再大一点但完全自包含。排查时用objdump -p app.exe | findstr /i "DLL Name"看依赖清单,哪些是系统自带的、哪些是 MinGW 的,一目了然。
5.4 现象:源码里加了中文注释就报stray '\357' in program
原因是编码不对。GCC 12 默认按 UTF-8 解析源文件,如果文件被编辑器存成了 GBK,中文注释的字节序列就会被当成非法字符。这里有个反直觉的地方:报错位置常常指向注释后面那一行,看起来像是那行代码有问题,其实是前一行的注释在解码时炸了。
解决:把源文件统一存成 UTF-8(不带 BOM),编辑器里设一下默认编码。实在不能改文件,编译时补-finput-charset=GBK -fexec-charset=GBK,让编译器按 GBK 读、按 GBK 输出。控制台里字符串显示成乱码是另一回事,那是终端代码页的问题,chcp 65001切到 UTF-8 就能解决,跟编译本身无关。
5.5 现象:明明装了 12.2.0,gcc -v显示的却是别的版本
这是多套工具链共存的典型症状。where gcc往往会列出两三行,第一行指向的可能是某个 IDE 自带的 9.x 或者某个包管理器装的版本,PATH 顺序决定了谁先被命中。更隐蔽的一种情况是环境变量C_INCLUDE_PATH、CPLUS_INCLUDE_PATH、LIBRARY_PATH残留着指向旧工具链的路径,编译出来的东西头文件来自 A、库来自 B,报错信息会非常离谱。
解决:先where gcc和gcc -v确认实际命中的是哪一份,gcc -v输出里的Target和Thread model两行也能一并核对。然后清理那几个残留的*_INCLUDE_PATH类变量,最后调整 PATH 顺序,把 12.2.0 的bin放到最前面。我一般直接把旧的那条从 PATH 里删掉,让机器上只留一套,省得以后又撞上。
6. 装完怎么验证:三条命令加一个最小工程
装完的验证我从来不看“能打开”这种模糊标准,而是固定跑三条命令,每条对应一个可能的错法。
# 第一条:确认目标架构,输出 x86_64-w64-mingw32 才对 gcc -dumpmachine # 第二条:确认版本号首行是 12.2.0 gcc --version # 第三条:确认命中的只有你装的那一份 where gcc三条命令的预期结果和异常含义对照如下:
| 命令 | 预期输出 | 不符合时说明什么 |
|---|---|---|
gcc -dumpmachine | x86_64-w64-mingw32 | 出现i686说明装成了 32 位包 |
gcc --version | 首行含12.2.0 | 版本号不对说明 PATH 命中了别的工具链 |
where gcc | 只有一行且指向...\mingw64-12.2.0\bin\gcc.exe | 多行说明机器上还有第二套,早晚串味 |
三条都过了,再跑一个最小工程收尾。不用 Hello World,直接用一段带标准库和异常的东西,把 C++ 运行库链路也验证掉:
// verify.cpp —— 同时验证 STL、异常和 -static 链接是否生效 #include <iostream> #include <stdexcept> #include <string> int main() { try { throw std::runtime_error("verify ok"); } catch (const std::exception& e) { std::cout << e.what() << " / gcc " << __GNUC__ << '\n'; } return 0; }g++ -std=c++17 -O2 -static verify.cpp -o verify.exe .\verify.exe objdump -p verify.exe | findstr /i "DLL Name"objdump那条是关键,-static生效的话,依赖里应该只剩KERNEL32.dll、msvcrt.dll、USER32.dll这类系统 DLL,看不到libstdc++-6.dll。能看到它,说明静态链接没生效,八成是命令里漏了开关或者被后面的参数覆盖了。
从那以后我每次装工具链,不管多急,都会把gcc -dumpmachine、gcc --version、where gcc这三条先走一遍,再跑那个带异常的验证程序。多花三分钟,能省掉后面半天“明明装了却编不过”的玄学排查。希望帮到你。
本文还有配套的精品资源,点击获取