c - 什么是/ 解析的外部符号的未定义引用错误, 如何修复它呢?

  显示原文与译文双语对照的内容

未定义的引用/未解析的外部符号错误? 什么是常见原因以及如何修复/阻止它们?

自由编辑/添加你自己的。

时间:

编译 C++ 程序的步骤有几个步骤,如 2.2 ( credits 。汤普森的致谢信) 所示:

翻译语法规则的优先级由以下阶段 [see footnote] 指定。

  1. 如有必要,物理源文件字符以implementation-defined方式映射到基本源字符集( 引入new-line字符end-of-line指标) 。 [SNIP]
  2. 反斜杠字符( )的每个实例紧接着一个new-line字符被删除,将物理源行拼接成逻辑源行。 [SNIP]
  3. 源文件被分解为预处理标记( 2.5 ) 和white-space字符( 包括评论)的序列。 [SNIP]
  4. 执行预处理指令,展开宏调用,并执行_Pragma一元运算符表达式。 [SNIP]
  5. 每个源字符集成员字符文字或一个字符串,以及每个转义序列和universal-character-name字符文字或non-raw字符串,转化为相应的成员执行字符集; [SNIP]
  6. 相邻的字符串文字标记被连接。
  7. 分隔标记的White-space字符不再有效。 每个预处理标记都转换为一个标记。 ( 2.7 ) 生成的标记在句法和语义上都是分析的,并翻译为翻译单元。 [SNIP]
  8. 翻译翻译单元和实例化单元的组合如下: [SNIP]
  9. 对所有外部实体引用进行磅的解析。 库组件链接以满足对当前翻译中没有定义的实体的外部引用。 所有翻译器的输出被收集成一个程序映像,其包含在运行环境中执行所需要的信息。 ( 强调地雷)

[footnote] 实现必须像如果出现这些不同的阶段,虽然在实践中不同阶段可能会折叠在一起。

指定的错误在编译的最后阶段发生,通常称为链接。 它基本上意味着你将一系列的实现文件编译到对象文件或库中,现在你想让它们一起工作。

说你定义符号 aa.cpp 。 现在,b.cpp 声明了符号并使用它。 连接之前,它只是假设,符号定义的某个地方,但它确实没有护理。 链接阶段负责查找符号并正确链接到 b.cpp ( 实际上,对使用它的对象或库) 。

如果你使用 MSVS,你将看到项目生成 .lib 文件。 这些包含导出符号的表格和导入符号的表格。 导入的符号根据你链接的库解析,导出的符号为使用 .lib ( 如果有的话)的库提供。

其他编译器/平台也存在类似的机制。

常见的错误消息是 error LNK2001error LNK1120MSVSundefined reference to symbolName 用于的gcc

如下所示代码:


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

MSVS的类似错误︰


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

常见原因包括:

类成员:

virtual 析构函数需要一个实现。

声明析构函数纯仍然需要你定义它:


struct X
{
 virtual ~X() = 0;
};
struct Y : X
{
 ~Y() {}
};
int main()
{
 Y y;
}
//X::~X(){}//uncomment this line for successful definition

这是因为在隐式销毁对象时调用基类析构函数,因此需要定义。

virtual 方法必须实现或者定义为纯方法。

这类似于没有定义的非 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;
};

virtual 类成员

即使没有显式使用,也需要定义一些成员:


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

可以为类定义中的整型或者枚举类型的staticconst 数据成员提供初始值设定项;但是,这里成员的odr-use仍需要如上所述的命名空间作用域定义。 C++11允许在类内部初始化所有 static const 数据成员。

声明但没有定义变量或者函数。

一个典型的变量声明是

 
extern int x;

 

因为这只是一个声明,需要一个的单一定义 。 相应的定义是:

 
int x;

 

例如以下将产生一个错误:


extern int x;
int main()
{
 x = 0;
}
//int x;//uncomment this line for successful definition

类似的注释应用于函数。 声明一个函数而不定义它将导致错误:


void foo();//declaration only
int main()
{
 foo();
}
//void foo() {}//uncomment this line for successful definition

请注意,你实现的函数与你所声明的函数完全匹配。 例如你可能有不匹配的cv-qualifiers:


void foo(int& x);
int main()
{
 int x;
 foo(x);
}
void foo(const int& x) {}//different function, doesn't provide a definition
//for void foo(int& x)

不匹配的其他示例包括

  • 在一个命名空间中声明的函数/变量,在另一个命名空间中定义。
  • 声明为类成员的函数/变量,定义为全局( 反之亦然) 。
  • 函数返回类型,参数数和类型以及调用约定都不完全一致。

来自编译器的错误消息通常会给出声明但从未定义的变量或者函数的完整声明。 你提供的定义比较。 确保每个细节都匹配。

无法链接到适当的库/对象文件或者编译实现文件

通常,每个翻译单元都会生成一个对象文件,其中包含在翻译单元中定义的符号的定义。 要使用这些符号,你必须链接那些对象文件。

gcc你会指定所有对象文件 命令行 联系在一起,或编译实现文件组合在一起。


g++ -o test objectFile1.o objectFile2.o -lLibraryName

XCode:添加用户标题搜索路径-> 添加库搜索路径> 拖放实际的库引用到项目文件夹。

MSVS 下,添加到项目中的文件会自动将它的对象文件链接在一起,并生成一个 lib 文件。 要在单独的项目中使用符号,你需要在项目设置中包含 lib 文件。 在项目属性的链接器部分中完成,在 Input -> Additional Dependencies ( lib 文件的路径应该在 Linker -> General -> Additional Library Directories ) 当使用 lib 文件提供的第三方库时,失败通常会导致错误。

也可能会忘记将文件添加到编译中,在这种情况下,不会生成对象文件。 在 gcc 命令行 添加的文件。 在 MSVS 中添加文件将使它自动编译。

在 Windows 编程中,没有链接必要库的tell-tale符号是未解析符号的名称以 __imp_ 开头。 在文档中查找函数的名称,它应该说明你需要使用哪个库。 例如MSDN将信息放在一个名为"程序库"的节中每个函数底部的框中。

模板实现不可见。

非专用化的模板必须对所有使用它们的翻译单位都可见。 这意味着你不能将模板的定义与实现文件分离。 如果你必须分离实现,通常的解决办法是拥有一个 impl 文件,该文件在声明模板的头部的末尾包含一个文件。 常见情况是:


template<class T>
struct X
{
 void foo();
};

int main()
{
 X<int> x;
 x.foo();
}

//differentImplementationFile.cpp
template<class T>
void X<T>::foo()
{
}

要解决这个问题,你必须将 X::foo的定义移动到头文件或者对使用它的翻译单元可见。

可以在实现文件中实现专用模板,而实现不必是可见的,但必须事先声明专用化。

有关进一步的解释和另一个可能的解决方案( 显式实例化),请参阅这个问题并回答

符号在一个C 程序中定义并在 C++ 代码中使用。

函数( 或者变量) void foo() 是在一个C 程序中定义的,你试图在 C++ 程序中使用它:


void foo();
int main()
{
 foo();
}

C++ 链接器要求名称被破坏,因此你必须将函数声明为:


extern"C" void foo();
int main()
{
 foo();
}

等价地,函数( 或者变量) void foo() 不是在一个C 程序中定义的,而是在 C++ 中定义的,但使用了C 链接:


extern"C" void foo();

你试图在 C++ 程序中使用 C++ 链接。

如果整个库包含在头文件( 编译为C 代码) 中;则包含需要如下:


extern"C" {
 #include"cheader.h"
}

错误导入/导出方法/类accross模块。 ( 编译器特定)

MSVS要求你使用 __declspec(dllexport)__declspec(dllimport) 指定要导出和导入的符号。

这个双重功能通常通过使用宏获得:


#ifdef THIS_MODULE
#define DLLIMPEXP __declspec(dllexport)
#else
#define DLLIMPEXP __declspec(dllimport)
#endif

THIS_MODULE 只能在导出函数的模块中定义。 这样,声明:


DLLIMPEXP void foo();

扩展到


__declspec(dllexport) void foo();

并告诉编译器导出函数,因为当前模块包含它的定义。 当在不同的模块中包含声明时,它将扩展到


__declspec(dllimport) void foo();

并告诉编译器定义位于你与( 也看到 1 ) 链接的库中。

你可以导入/导出类:


class DLLIMPEXP X
{
};

如果所有其他都失败,则为 ,重新编译。

我最近能够在 Visual Studio 2012中删除一个未解决的外部错误,只需重新编译有问题的文件。 当我re-built时,错误消失了。

这通常发生在两个( 或者更多) 库具有循环依赖关系时。 库尝试在 B.lib 和库中使用符号 A.lib. 尝试使用中的符号,但不存在。 试图编译时,链接步骤将失败,因为找不到 B.lib. A.lib 将生成,但没有 dll 。 然后编译 B.lib.,它将成功并生成 Re-compiling A,因为 B.lib 现在已经找到了。

指定相互依赖链接库的顺序错误。

如果库依赖于彼此,那么链接库的顺序会发生什么。 一般来说,如果图书馆 AB 取决于图书馆,然后 libA必须朝见 libB 连接程序中国旗。

例如:


//B.h
#ifndef B_H
#define B_H

struct B {
 B(int);
 int x;
};

#endif

//B.cpp
#include"B.h"
B::B(int xx) : x(xx) {}

//A.h
#include"B.h"

struct A {
 A(int x);
 B b;
};

//A.cpp
#include"A.h"

A::A(int x) : b(x) {}

//main.cpp
#include"A.h"

int main() {
 A a(5);
 return 0;
};

创建库:


$ g++ -c A.cpp
$ g++ -c B.cpp
$ ar rvs libA.a A.o 
ar: creating libA.a
a - A.o
$ ar rvs libB.a B.o 
ar: creating libB.a
a - B.o

编译:


$ g++ main.cpp -L. -lB -lA
./libA.a(A.o): In function `A::A(int)':
A.cpp:(.text+0x1c): undefined reference to `B::B(int)'
collect2: error: ld returned 1 exit status
$ g++ main.cpp -L. -lA -lB
$./a.out

所以要再次重复,订单 !

"未定义的引用/未解析的外部符号"

我会试着解释什么是"未定义的引用/未解析的外部符号"。

注意:我使用 G++ 和 Linux,所有示例都适用于它

note2: 很抱歉,我只是在学习。 请随意编辑我的答案:

例如我们有代码


//src1.cpp
void print();

static int local_var_name;//'static' makes variable not visible for other modules
int global_var_name = 123;

int main()
{
 print();
 return 0;
}


//src2.cpp
extern"C" int printf (const char*,.. .);

extern int global_var_name;
//extern int local_var_name;

void print ()
{
//printf("%d%dn", global_var_name, local_var_name);
 printf("%dn", global_var_name);
}

制作对象文件


$ g++ -c src1.cpp -o src1.o
$ g++ -c src2.cpp -o src2.o

在汇编阶段之后,我们有一个对象文件,其中包含要导出的任何符号。 看看这些符号


$ readelf --symbols src1.o
 Num: Value Size Type Bind Vis Ndx Name
 5: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 _ZL14local_var_name # [1]
 9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 global_var_name # [2]

我拒绝了输出中的一些行,因为它们不重要

所以,我们看到了要导出的符号。


[1] - this is our static (local) variable (important - Bind has a type"LOCAL")
[2] - this is our global variable

src2.cpp 没有导出,我们将不会看到它的符号

链接我们的目标文件


$ g++ src1.o src2.o -o prog

并运行它

 
$. /prog
123

 

在导出的sysmbols处看到链接器并链接它。 现在我们将尝试在 src2.cpp 中取消注释行,如下所示


//src2.cpp
extern"C" int printf (const char*,.. .);

extern int global_var_name;
extern int local_var_name;

void print ()
{
 printf("%d%dn", global_var_name, local_var_name);
}

重建对象文件


$ g++ -c src2.cpp -o src2.o

好的,因为我们只构建对象文件,链接还没有完成。 尝试链接


$ g++ src1.o src2.o -o prog
src2.o: In function `print()':
src2.cpp:(.text+0x6): undefined reference to `local_var_name'
collect2: error: ld returned 1 exit status

之所以发生,是因为我们的local_var_name是静态的,IE 对于其他模块来说是不可见的。 我们将更深入地理解。获取转换阶段输出


$ g++ -S src1.cpp -o src1.s

//src1.s
look src1.s

. file"src1.cpp"
. local _ZL14local_var_name
. comm _ZL14local_var_name,4,4
. globl global_var_name
. data
. align 4
. type global_var_name, @object
. size global_var_name, 4
global_var_name:
. long 123
. text
. globl main
. type main, @function
main:
; assembler code, not interesting for us
.LFE0:
. size main,. -main
. ident"GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
. section. note.GNU-stack,"",@progbits

所以,我们看到没有local_var_name的标签,这就是为什么链接器没有找到它。 但我们是黑客,我们可以修复它。 打开 src1.s texteditor和改变


.local _ZL14local_var_name
.comm _ZL14local_var_name,4,4


. globl local_var_name
. data
. align 4
. type local_var_name, @object
. size local_var_name, 4
local_var_name:
. long 456789

IE 你应该像风箱一样


. file"src1.cpp"
. globl local_var_name
. data
. align 4
. type local_var_name, @object
. size local_var_name, 4
local_var_name:
. long 456789
. globl global_var_name
. align 4
. type global_var_name, @object
. size global_var_name, 4
global_var_name:
. long 123
. text
. globl main
. type main, @function
main:
;.. .

我们将更改local_var_name的可见性,并将它的值设置为 456789尝试从它构建对象文件


$ g++ -c src1.s -o src2.o

好,查看readelf输出( 符号)


$ readelf --symbols src1.o
8: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 local_var_name

现在local_var_name已经绑定全局( 是本地的)

链接


$ g++ src1.o src2.o -o prog

并运行它

 
$. /prog 
123456789

 

好,我们 hack:

因此,当链接器无法在对象文件中找到全局符号时发生"未定义的引用/未解析的外部符号错误"。

...