编译链接错误对照表

看到红字别慌——先分清是编译错误还是链接错误,再对号入座

通识重叠

◎学完本节你会

本页所有报错原文都是真实的:逐条用本机 g++ 15.2 / MSYS2 MinGW64 编译复现, 直接复制编译器输出,没有手写"大概是这样"。每条的"修法"也都实测编译通过。 你遇到的报错可能因为编译器/系统不同而措辞略有差异,但关键词是一样的。

1排错第一步:这是编译错误还是链接错误?

新手最容易犯的错,是把链接错误当成语法错误去逐行改代码——方向就错了。 其实两者一眼能分:

特征编译期错误链接期错误
报错里有没有只有 error: 行有 ld.exe / collect2.exe
指不指具体行列指(如 e6.cpp:1:13)指 .o 文件与符号,不指你的源码行
典型关键词was not declared、expected、cannot convertundefined reference、multiple definition、cannot find -l
该往哪改报错指的那一行源码缺的定义在哪、库链没链上——不在报错那行
一句话口诀:看到 ld.exe、collect2、undefined reference、multiple definition → 链接期,别改报错那一行;只有 error: 且带 文件:行:列 → 编译期,就改那一行。

练一练:下面这条是哪一类?

(点"换一条"开始)

213 条高频报错对照表

把你遇到的报错关键词粘进搜索框(如 undefined reference、vtable、bad_alloc), 或点上面的阶段标签筛选。每条都是报错原文 → 真实原因 → 修法三栏。

!排错时最容易走偏的 4 件事

① 把链接错误当语法错误:undefined reference 不是"这行代码写错了", 而是"这个符号的定义没链接进来"。盯着报错那行改半天也没用,要去找定义。
② 报错行号会"错位":漏分号(E7)时,编译器往往指向下一行。 看到 expected ';' before X,要回头查 X 的上一行末尾。
③ 第一条 error 才是真凶:一个错会引发一连串"连锁报错"。 只看第一个 error:,改完再编,后面的常常自己就消失了。
④ 以为 std::bad_alloc 是编译错误(E13):它编译能过,是运行时 new 要不到内存才抛的。要在运行时 catch,不是去改编译选项。

✎练习题

Q1. 报错 undefined reference to 'foo()' 且前面有 ld.exe,应该?
有 ld.exe + undefined reference = 链接期错误(E1/E3/E5)。链接器找不到 foo() 的定义,跟"哪一行调用了它"无关。要检查:定义写了吗?定义所在的 .cpp 参与编译/链接了吗?声明和定义的签名一致吗?
Q2. multiple definition of 'g_count' 最常见的根因是?
这是 E2。头文件里写 int g_count = 0; 是定义,每个 include 它的 .cpp 都会得到一份,链接时就"重复定义"。修法:头文件改成 extern int g_count;(只声明),真正的定义只放进一个 .cpp。
Q3. Makefile 报 missing separator,通常是因为?
这是 E12。Makefile 的规则命令行必须以 TAB 开头,用空格(哪怕是 8 个空格)会报 missing separator。这是 Makefile 最经典的坑——很多编辑器的"自动缩进"会偷偷把 TAB 变成空格。

Q4. 你写了个模板 template<class T> T max2(T a,T b); 声明在 util.h, 实现放在 util.cpp,另一个文件 include 后调用 max2(3,7),链接时报 undefined reference to 'int max2<int>(int,int)'。为什么?怎么修?

答案:这是 E5。原因:模板不是普通函数——编译器只有在看到实例化点(如 max2(3,7)) 且同时能看到模板定义时,才能生成 max2<int> 的代码。定义藏在 util.cpp 里, 调用方 include 的头文件只有声明,于是链接期找不到实例。
修法:把模板的实现也写进头文件(util.h)。这是模板的铁律,也是"模板为什么都写在头文件"的原因(对应 30.4)。

Q5. 链接报 undefined reference to `vtable for Base', 但你觉得 Base 的函数都写了。可能漏了什么?

答案:这是 E3。vtable(虚表)由编译器生成,生成的触发点是类里第一个非内联、非纯虚的虚函数的定义。 如果你声明了 virtual void f(); 却忘了写它的实现,编译器就没地方挂虚表,链接期报 undefined reference to vtable。
修法:① 给那个虚函数补上实现;或 ② 若本就想让它抽象,改成纯虚 virtual void f() = 0;(那样就该实例化派生类而非基类)。

Q6. 一个跨平台工程,本地能编译链接通过,同事用某个预编译第三方库时却报 undefined reference to 'get_name[abi:cxx11]()'。报错里那个 [abi:cxx11] 说明了什么?怎么定位?

答案:这是 E10,ABI 不兼容。[abi:cxx11] 表示调用方在找 C++11 新 ABI 版本的 std::string 符号(std::__cxx11::basic_string),但预编译库里导出的是旧 ABI (_GLIBCXX_USE_CXX11_ABI=0)的符号——两者符号名不同,链接器自然找不到。
定位:① 看报错里的 [abi:cxx11] 或 __cxx11 字样; ② 用 nm -C 库.a | grep 符号名 看库导出的到底是哪个 ABI 版本; ③ 修法:让主程序和第三方库用同一套 _GLIBCXX_USE_CXX11_ABI 设置重新编译, 或换用与你的标准库 ABI 匹配的预编译版本(对应 40.1 依赖与 ABI 兼容)。

◎跨学科小贴士:为什么"编译"和"链接"要分两步

C++ 把"翻译"拆成编译(每个 .cpp 各自翻译成 .o)和链接(把一堆 .o 拼成可执行文件)两步, 这和盖楼先预制构件再现场组装是一个道理。

好处是增量:改一个 .cpp 只需重编那一个 .o,再重新链接,不用全部重来——这就是 Makefile/CMake 存在的意义(26.4/26.5)。 代价就是错误也分两类:构件本身造错了(编译期,指着某个 .cpp 的某行), 和构件之间对不上、螺丝孔不匹配(链接期,说某个符号找不到或重复)。

所以读懂报错属于哪一类,等于知道该去"造构件"还是"组装"环节找问题——这比盲目改代码高效得多。 阶段五项目 A(自制一门小语言)你会亲手写"编译"这一半;真正的链接器是另一门大学问。