make 编译器内部崩溃 internal compiler error 速查
更新时间:2026-10-24。FAQ 速查页,面向用 GNU make 驱动 C/C++ 构建的同学。make 编译器内部崩溃指编译过程不是「代码写错被报错」,而是编译器进程自己死了:要么打出
internal compiler error后自尽,要么cc1plus被系统 OOM killer 一枪爆头只留下一句Killed。make 本身只是传声筒——真正死的是它拉起来的 gcc/g++ 子进程,而 make 的回显(Error 4、Segmentation fault)把真相盖住了。这类故障最常出现在大内存机器上开-j并行编译、或编译超大模板文件的 C++ 工程。本篇以报错原文为锚逐条定位;如果是代码本身的语法/类型报错,请转 error: cannot convert 类型不匹配速查;如果是规则缺失,请转 make: No rule to make target 报错速查。
本文要回答的问题
internal compiler error是我的代码错了还是编译器错了?- 报错里的
cc1plus、Error 4、Killed分别是谁在说话? - 为什么
make -j16必崩、make -j4就好好的? - OOM killer 杀编译进程这件事怎么确认?
- 编译器 bug 怎么绕过、怎么上报?
一、报错原文与典型现象
两种典型形态(工程路径以 /proj/demo 占位)。形态 A,编译器自己认怂:
$ make -j8
g++ -O2 -c src/engine.cpp -o build/engine.o
during GIMPLE pass: gimple-low
src/engine.cpp: In member function 'void Engine::step()':
src/engine.cpp:812:5: internal compiler error: Segmentation fault
812 | dispatch(state);
| ^~~~~~~~
0x7f3c9a2b108f ???
../sysdeps/unix/sysv/linux/x86_64/sigaction.c:0
Please submit a full bug report,
with preprocessed source if appropriate.
See <https://gcc.gnu.org/bugs/> for instructions.
make: *** [Makefile:24: build/engine.o] Error 1形态 B,OOM killer 干的,编译器连遗言都没留:
$ make -j16
g++ -O2 -c src/render.cpp -o build/render.o
g++: fatal error: Killed signal terminated program cc1plus
compilation terminated.
make: *** [Makefile:31: build/render.o] Error 4读懂现场抓住三件东西:谁死了(cc1plus 是 C++ 编译器本体,cc1 是 C、as 是汇编器、collect2/ld 是链接器——报错里被点名的那个进程才是真死者)、怎么死的(Segmentation fault 多为编译器 bug 或硬件内存问题,Killed 即信号 9、几乎百分百是 OOM killer)、exit code(Error 4 是信号 4 SIGILL 或 gcc 对被杀子进程的汇总码,Error 1 是编译器主动退出)。make 的 *** [Makefile:NN: xxx.o] Error K 只是汇总行,真线索在它上面几行。
二、病因处置速查表
| 报错现象 / 场景 | 可能原因 | 快速验证 | 处置 |
|---|---|---|---|
Killed signal terminated program cc1plus,只在 -j 大时出现 | 并行编译内存超限,OOM killer 出手 | dmesg -T | grep -i "killed process" 有 cc1plus 记录 | 降 -j 到 内存GB/2 左右,或加 swap/zram |
固定某一个 .cpp 编到一半段错误 | 该文件模板/constexpr 展开太大,单进程内存爆 | make -j1 单独编这个文件,看 /usr/bin/time -v 峰值内存 | 拆分文件、降 -O 级别、换预编译头 |
internal compiler error: Segmentation fault,换台机器同版本就过 | 本机内存条/超频不稳 | 跑 memtest86+ 或多次重编同文件结果不一致 | 排查硬件;降频关 XMP |
| 同文件同参数必现 ICE | 编译器自身 bug | 换相邻 gcc/clang 版本重编同文件 | 升降级编译器;用 -E 产预处理文件上报 |
编译中途 No space left on device 后 ICE | /tmp 或构建目录所在盘写满 | df -h /tmp 与构建分区 | 清盘或 TMPDIR 指到大分区 |
| 系统头文件被改/混装多套工具链 | 头文件损坏或版本错配 | g++ -v 看 include 搜索路径是否混入异版本 | 重装工具链、清缓存目录 |
三、排查决策图
下面这张图按「谁先开口、说了什么」分岔:红是报错入口与命中的根因,蓝是排查动作,绿是通过分支,橙是修复动作,紫是防复发沉淀。
读这张图抓一条主线:先判死因再动手。Killed 字样基本等于 OOM,直接查 dmesg 实锤后降并行;internal compiler error 才有编译器 bug 与硬件两条岔路,用「换版本、换机器、反复编」三板斧区分;磁盘满是最便宜的病灶,df -h 一秒排掉。三条修完都要 -j1 复编且连续多次结果一致才算闭环——间歇性段错误最怕「碰巧过了」。
四、逐条排查展开
先让 OOM killer 现形。 Linux 内存耗尽时由内核挑「最占内存」的进程杀掉,编译期被杀的几乎总是 cc1plus:
dmesg -T | grep -iE "out of memory|killed process"
# 典型输出:Out of memory: Killed process 48213 (cc1plus) total-vm:6.8G ...
journalctl -k --since "1 hour ago" | grep -i oom # systemd 系统只要看到 cc1plus 出现在 oom-kill 名单里,根因就锁死了,不用再怀疑代码。
并行度与内存的换算经验。 一个 cc1plus 编普通 C++ 文件峰值 0.5-1.5GB,重度模板(boost、Eigen、大型 constexpr)单进程能吃 3-6GB。经验公式:-j 上限 ≈ 可用内存GB ÷ 2。8GB 内存开 -j16 必崩不是编译器不行,是数学不行。make 4.3+ 支持 -j 配 -l 负载阈值(make -j16 -l8:负载超 8 就不再开新作业),比死 -j 温和;更稳的做法是给个别巨型文件单独建规则降优化级别。
区分编译器 bug 与硬件不稳。 判据是可复现性:同一文件、同一参数、同一机器每次都在同一行 ICE——大概率编译器 bug;同一文件这次过下次崩、崩的位置还漂移——先怀疑内存条。前者用相邻版本验证(gcc 12 崩就试 gcc 11 或 13,clang 同理),后者跑 memtest 一夜。服务器 ECC 内存的报错记录在 rasdaemon 或 edac-util 里能查到。
编译器 bug 的绕过与上报。 上报前 gcc 会提示「Please submit a full bug report, with preprocessed source」——用 g++ -E 对同参数产 .ii 预处理文件,连同 g++ -v 输出一起提交。绕过技巧按命中率排序:对肇事文件降 -O2 到 -O1(很多 ICE 在特定优化 pass 里,报错行的 during GIMPLE pass: xxx 会点名)、关掉可疑的新开关(-flto、-fanalyzer 是 ICE 大户)、把报错行附近的模板/宏展开改写成更朴素的等价写法。
磁盘与 TMPDIR。 gcc 编译期的临时文件(.s 汇编中间稿、cc1plus 的临时映射)默认落 /tmp;很多容器与 CI 镜像 /tmp 只有几百 MB 或是 tmpfs 与内存共享——一边吃内存一边写 tmpfs 会双重爆。TMPDIR=/proj/demo/tmp g++ ... 指到大盘即可,makefile 里可以 export TMPDIR := $(CURDIR)/.tmp。
工具链混装的暗坑。 系统里装过多套 gcc 时,g++ 本体与 cc1plus、libstdc++ 头文件可能来自不同版本(常见于手工解压安装又加了 PATH)。g++ -v 末尾的 include 搜索顺序、以及 g++ -print-prog-name=cc1plus 指出的 cc1plus 路径必须同族,错配会以各种诡异 ICE 收场。
链接阶段的崩溃另算一笔。 报错里被点名的是 collect2 或 ld 时,死者是链接器:巨型静态库、LTO 全程序优化、调试信息(-g)都会让 ld 的内存峰值远超单个 cc1plus——同样吃 OOM,但药方不同:减 -g 为 -g1、用 lld 或 mold 这类内存友好的链接器、或给 LTO 降并行(-flto=4)。
五、规范做法与预防
- 并行度写进构建脚本且按机器算:
make -j"$(nproc)"在多核小内存机上是定时炸弹,改成按内存取上限(如nproc与 内存GB/2 取小者)。 - CI 记录版本指纹:每次构建日志里留
g++ --version、df -h、free -h三行快照,事后排查不用猜环境。 - 巨型翻译单元单独治理:单文件编译峰值超 2GB 的就拆——模板显式实例化外移、Pimpl 割断头文件依赖、Unity build 慎用于大工程。
- 开启 ccache 但要监控盘:ccache 大幅缩短重编时间,缓目录满了一样引发写失败,定期
ccache -s看命中率与占用。 - 上报过的 bug 记台账:团队 wiki 记录「gcc 某版本 + 某写法 = ICE」的已知坑与绕过写法,新人少踩一遍。
这一篇在知识体系里的位置
本篇是 Makefile 排错速查簇的编译器崩溃篇,定位「make 起来了、编译器进程死了」:语法解析阶段就 Stop 的看 Makefile missing separator 语法解析报错速查;头文件找不到的看 fatal error 头文件找不到速查;类型接口报错的看 error: cannot convert 类型不匹配速查;链接期符号问题的看 undefined reference 链接错误速查;权限问题的看 构建 Permission denied 报错速查;规则缺失的看 make: No rule to make target 报错速查;构建卡死不动的看 make 构建卡死无响应速查;输出乱码的看 make 构建输出乱码速查;CMake 侧的看 CMake 版本不兼容报错速查、CMake 找不到编译器速查 与 CMake FetchContent 拉取失败速查。「先 dmesg 查 OOM 再谈其它」这一招对所有进程莫名被杀类故障通用。
一句话总结
make 编译器内部崩溃先判死因:Killed signal terminated program cc1plus 就 dmesg 查 OOM 名单、实锤后把 -j 降到 内存GB/2 或加 swap;internal compiler error: Segmentation fault 则用「换编译器版本、换机器、反复编」区分编译器 bug 与硬件不稳——可复现的换版本绕过并对肇事文件降 -O,不可复现的去跑 memtest;顺手 df -h 排掉 /tmp 写满、g++ -v 排掉工具链混装。CI 留版本与内存快照、巨型翻译单元单独降优化,这类崩溃即可从「灵异事件」降格为「例行算术」。