什么是未定义的引用/未解决的外部符号错误,我该如何修复它?
类成员:
纯虚析构函数需要有实现。
虽然声明了一个析构函数为纯虚函数,但仍需要定义实现(与一个普通函数不同):
struct X { virtual ~X() = 0; }; struct Y : X { ~Y() {} }; int main() { Y y; } //X::~X(){} //uncomment this line for successful definition
因为当对象隐式销毁时,基类析构函数会被调用,所以需要定义它。
virtual
方法必须要么有实现,要么定义为纯函数。
这类似于没有定义的非虚拟方法,但增加了一个纯函数声明,生成一个虚表,如果没有使用函数,则可能导致链接错误:
struct X { virtual void foo(); }; struct Y : X { void foo() {} }; int main() { Y y; //linker error although there was no call to X::foo }
为了使其正常工作,请将X::foo()
声明为纯函数:
struct X { virtual void foo() = 0; };
非虚拟类成员
一些成员即使没有显式使用仍需要定义:
struct A { ~A(); };
下面的代码将会产生错误:
A a; //destructor undefined
可以在类定义本身中内联实现:
struct A { ~A() {} };
也可以在类定义外部实现:
A::~A() {}
如果实现在头文件中但不在类定义中,方法必须标记为inline
以防止多重定义。
如果使用了成员方法,则需要定义所有使用过的成员方法。
一个常见的错误是忘记限定名称:
struct A { void foo(); }; void foo() {} int main() { A a; a.foo(); }
应该这样定义:
void A::foo() {}
static
数据成员必须在一个单独的翻译单元中定义:
struct X { static int x; }; int main() { int x = X::x; } //int X::x; //uncomment this line to define X::x
对于定义为整数或枚举类型的static
const
数据成员可以在类定义中提供初始化值;然而,对此成员的odr-use仍需要一个命名空间范围的定义,如上所述。C++11允许为所有static const
数据成员提供类内初始化。
编译C++程序需要经过几个步骤,如2.2(引用Keith Thompson的参考资料)所述:
语法翻译的优先级由以下阶段指定[见脚注]。
- 如果需要,物理源文件字符将以实现定义的方式映射到基本源字符集中(引入换行符作为行末指示符)。[剪裁]
- 每个反斜杠字符(\)后紧接着一个换行符都将被删除,拼接物理源行以形成逻辑源行。[剪裁]
- 源文件被分解成预处理标记(2.5)和空白字符序列(包括注释)。[剪裁]
- 执行预处理指令、展开宏调用和执行_Pragma一元运算符表达式。[剪裁]
- 每个字符字面值或字符串字面值中的源字符集成员,以及每个字符字面值或非原始字符串字面值中的转义序列和通用字符名称,都将转换为相应的执行字符集成员;[剪裁]
- 相邻的字符串字面值标记被连接。
- 分隔标记的空白字符不再重要。每个预处理标记将转换为一个标记。(2.7)。得到的标记将被句法和语义分析,并作为一个翻译单元进行翻译。[剪裁]
- 翻译的翻译单元和实例化单元将如下合并:[剪裁]
- 所有外部实体引用都将被解析。库组件将被链接以满足对当前翻译中未定义的实体的外部引用。所有这样的翻译输出都会被收集到一个程序映像中,该映像包含其执行环境所需的信息。(我强调)
[脚注]虽然实际上可能将不同的阶段折叠在一起,但实现必须表现得好像这些单独的阶段发生了。
指定的错误发生在编译的最后阶段,通常称为链接。这基本上意味着你将一堆实现文件编译成对象文件或库,现在你想让它们一起工作。
比如说你在 a.cpp 中定义了符号 a。现在,b.cpp 声明了这个符号并使用了它。在链接之前,它只是假设这个符号在某个地方被定义了,但它并不关心具体在哪里。链接阶段负责查找符号并将它正确地与 b.cpp (实际上是使用它的对象或库)链接起来。
如果你使用微软的 Visual Studio,你会看到项目生成 .lib 文件。这些文件包含一个导出符号的表和一个导入符号的表。导入的符号会与你链接到的库进行解析,导出的符号则提供给使用该 .lib 的库(如果有的话)。
其他编译器/平台也有类似的机制。
常见的错误消息是 Microsoft Visual Studio 中的 error LNK2001、error LNK1120、error LNK2019,以及 GCC 中的 undefined reference to symbolName。
代码:
struct X { virtual void foo(); }; struct Y : X { void foo() {} }; struct A { virtual ~A() = 0; }; struct B: A { virtual ~B(){} }; extern int x; void foo(); int main() { x = 0; foo(); Y y; B b; }
会生成与 GCC 类似的以下错误消息:
/home/AbiSfw/ccvvuHoX.o: In function `main': prog.cpp:(.text+0x10): undefined reference to `x' prog.cpp:(.text+0x19): undefined reference to `foo()' prog.cpp:(.text+0x2d): undefined reference to `A::~A()' /home/AbiSfw/ccvvuHoX.o: In function `B::~B()': prog.cpp:(.text._ZN1BD1Ev[B::~B()]+0xb): undefined reference to `A::~A()' /home/AbiSfw/ccvvuHoX.o: In function `B::~B()': prog.cpp:(.text._ZN1BD0Ev[B::~B()]+0x12): undefined reference to `A::~A()' /home/AbiSfw/ccvvuHoX.o:(.rodata._ZTI1Y[typeinfo for Y]+0x8): undefined reference to `typeinfo for X' /home/AbiSfw/ccvvuHoX.o:(.rodata._ZTI1B[typeinfo for B]+0x8): undefined reference to `typeinfo for A' collect2: ld returned 1 exit status
在 Microsoft Visual Studio 中则有类似的错误:
1>test2.obj : error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ) 1>test2.obj : error LNK2001: unresolved external symbol "int x" (?x@@3HA) 1>test2.obj : error LNK2001: unresolved external symbol "public: virtual __thiscall A::~A(void)" (??1A@@UAE@XZ) 1>test2.obj : error LNK2001: unresolved external symbol "public: virtual void __thiscall X::foo(void)" (?foo@X@@UAEXXZ) 1>...\test2.exe : fatal error LNK1120: 4 unresolved externals
常见的原因包括:
- 没有正确链接到适当的库/对象文件或编译实现文件;
- 变量或函数声明但未定义;
- 类型成员常见问题;
- 模板实现不可见;
- 符号在 C 程序中被定义,并在 C++ 代码中使用;
- 在模块/dll之间不正确地导入/导出方法/类(仅适用于 MSVS);
- 循环库依赖;
- undefined reference to `WinMain@16';
- 相互依赖库的顺序;
- 同名多个源文件;
- 使用 #pragma 时拼写错误或未包括 .lib 扩展名(Microsoft Visual Studio);
- 模板 friend 的问题;
- 不一致的 UNICODE 定义;
- const 变量声明/定义中缺少 "extern"(仅适用于 C++);
- Visual Studio Code 配置不适用于多文件项目;
- 在 Mac OS X 上构建动态链接库时出错,但在其他类 Unix 系统上构建 .so 没有问题。