提问人:Luchian Grigore 提问时间:9/25/2012 最后编辑:Jan SchultkeLuchian Grigore 更新时间:8/28/2023 访问量:940250
什么是未定义的引用/未解析的外部符号错误,如何解决?
What is an undefined reference/unresolved external symbol error and how do I fix it?
答:
假设您有以下代码:
// a.cpp
int get() { return 0; }
// b.cpp
int get(); // usually, one doesn't write this directly, but gets these
// declarations from included header files
int x = get();
编译时,编译器只是假设符号在某处定义,但它还不关心在哪里。链接阶段负责查找符号并正确链接由 和 生成的对象文件。b.cpp
get()
a.cpp
b.cpp
如果未定义,则会收到链接器错误,指出“未定义的引用”或“未解析的外部符号”。a.cpp
get
C++ 标准措辞
编译 C++ 程序分几个阶段进行,在 [lex.phases] 中指定,最后一个阶段是相关的:
9. 解析所有外部实体引用。 链接库组件以满足对当前转换中未定义的实体的外部引用。 所有这些转换器输出都被收集到一个程序映像中,该映像包含在其执行环境中执行所需的信息。
有关这些阶段的摘要,请参阅 Keith Thompson 的回答。
指定的错误发生在编译的最后阶段,通常称为链接。这基本上意味着您将一堆源文件编译为目标文件或库,现在您想让它们协同工作。
实践中的链接器错误
如果使用的是 Microsoft Visual Studio,则会看到项目生成文件。它们包含导出符号表和导入符号表。导入的符号将针对您链接的库进行解析,并且导出的符号将提供给使用该符号的库(如果有)。.lib
.lib
其他编译器/平台也存在类似的机制。
常见的错误消息是 Microsoft Visual Studio 的 、 和 GCC 的 symbolName。error LNK2001
error LNK1120
error LNK2019
undefined reference to
代码:
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)
- 循环库依赖关系
- 未定义对“WinMain@16”的引用
- 相互依赖的库顺序
- 多个同名的源文件
- 使用
#pragma
时键入错误或未包含 .lib 扩展名(Microsoft Visual Studio) - 模板好友的问题
- 不一致的
UNICODE
定义 - 常量变量声明/定义中缺少“extern”(仅限 C++)
- 未为多文件项目配置 Visual Studio Code
- 在 Mac OS X 上构建 dylib 时出错,但在其他 Unix-y 系统上使用 .so 是可以的
评论
unresolved symbol
inline
unresolved symbol
inline
未能链接到相应的库/目标文件或编译实现文件
通常,每个翻译单元都会生成一个目标文件,其中包含该翻译单元中定义的符号的定义。 若要使用这些符号,必须链接到这些对象文件。
在 gcc 下,您可以指定要在命令行中链接在一起的所有目标文件,或者将实现文件编译在一起。
g++ -o test objectFile1.o objectFile2.o -lLibraryName
-l...
必须位于任何 // 文件的右侧。.o
.c
.cpp
这里只是库的裸露名称,没有特定于平台的添加。例如,在 Linux 上,通常会调用库文件,但您只会编写 .在 Windows 上,可以调用相同的文件,但您将使用相同的参数。您可能需要使用 添加可以找到这些文件的目录。确保不要在 或 之后写空格。libraryName
libfoo.so
-lfoo
foo.lib
-L‹directory›
-l
-L
对于 Xcode:添加用户标头搜索路径 ->添加库搜索路径 -> 将实际的库引用拖放到项目文件夹中。
在 MSVS 下,添加到项目中的文件会自动将其目标文件链接在一起,并生成一个文件(通常使用)。要在单独的工程中使用符号,您需要
需要将文件包含在项目设置中。这是在项目属性的“链接器”部分中完成的。(文件的路径应为
添加于 ) 使用随文件提供的第三方库时,如果不这样做,通常会导致错误。lib
lib
Input -> Additional Dependencies
lib
Linker -> General -> Additional Library Directories
lib
也可能是忘记将文件添加到编译中,在这种情况下,不会生成目标文件。在 gcc 中,您可以将文件添加到命令行中。在 MSVS 中,将文件添加到项目中将使其自动编译(尽管可以手动将文件单独从生成中排除)。
在 Windows 编程中,未链接必要库的明显标志是未解析符号的名称以 开头。在文档中查找函数的名称,它应该说明您需要使用哪个库。例如,MSDN 将信息放在名为“库”的部分中每个函数底部的框中。__imp_
评论
gcc main.c
gcc main.c other.c
已声明但未定义变量或函数。
典型的变量声明是
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 限定符:
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)
其他不匹配的例子包括
- 在一个命名空间中声明的函数/变量,在另一个命名空间中定义。
- 声明为类成员的函数/变量,定义为全局(反之亦然)。
- 函数返回类型、参数编号和类型以及调用约定并不完全一致。
来自编译器的错误消息通常会为您提供已声明但从未定义的变量或函数的完整声明。将其与您提供的定义密切比较。确保每个细节都匹配。
评论
班级成员:
纯析构函数需要实现。virtual
声明析构函数 pure 仍然需要您定义它(与常规函数不同):
struct X
{
virtual ~X() = 0;
};
struct Y : X
{
~Y() {}
};
int main()
{
Y y;
}
//X::~X(){} //uncomment this line for successful definition
发生这种情况的原因是,当对象被隐式销毁时,会调用基类析构函数,因此需要定义。
virtual
方法必须实现或定义为纯方法。
这类似于没有定义的非方法,但增加了推理
纯声明生成一个虚拟的 vtable,如果不使用该函数,您可能会收到链接器错误: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
}
为此,请声明为 pure: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
可以为类定义中整数或枚举类型的数据成员提供初始值设定项;但是,此成员的 ODR 使用仍需要如上所述的命名空间范围定义。C++11 允许在类内对所有数据成员进行初始化。static
const
static const
评论
模板实现不可见。
非专用模板的定义必须对使用它们的所有翻译单元可见。这意味着您不能分离模板的定义
添加到实现文件。如果必须分离实现,通常的解决方法是在标头末尾包含一个文件
声明模板。常见的情况是: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++ 代码中使用。
函数(或变量)是在 C 程序中定义的,您尝试在 C++ 程序中使用它:void foo()
void foo();
int main()
{
foo();
}
C++ 链接器需要修改名称,因此必须将函数声明为:
extern "C" void foo();
int main()
{
foo();
}
等效地,函数(或变量)不是在 C 程序中定义的,而是在 C++ 中定义的,但带有 C 链接:void foo()
extern "C" void foo();
并且您尝试在具有 C++ 链接的 C++ 程序中使用它。
如果整个库包含在头文件中(并编译为 C 代码);包含内容需要如下;
extern "C" {
#include "cheader.h"
}
评论
#ifdef __cplusplus [\n] extern"C" { [\n] #endif
#ifdef __cplusplus [\n] } [\n] #endif
[\n]
extern "C" { #include <myCppHeader.h> }
错误地跨模块/dll 导入/导出方法/类(特定于编译器)。
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
{
};
评论
visibility
.def
.def
如果所有其他方法都失败,请重新编译。
我最近能够通过重新编译有问题的文件来摆脱 Visual Studio 2012 中未解决的外部错误。当我重新构建时,错误消失了。
当两个(或多个)库具有循环依赖关系时,通常会发生这种情况。库 A 尝试使用 B.lib 中的符号,库 B 尝试使用 A.lib 中的符号。两者都不存在。当您尝试编译 A 时,链接步骤将失败,因为它找不到 B.lib。将生成 A.lib,但不会生成 dll。然后编译 B,这将成功并生成 B.lib。重新编译 A 现在可以工作了,因为现在找到了 B.lib。
对WinMain@16
或类似的“不寻常”main()
入口点引用的未定义引用(尤其是对于 visual-studio)。
您可能错过了使用实际 IDE 选择正确的项目类型。IDE 可能希望将 Windows 应用程序项目绑定到此类入口点函数(如上面缺少的引用中指定),而不是常用的签名。int main(int argc, char** argv);
如果 IDE 支持普通控制台项目,则可能需要选择此项目类型,而不是 Windows 应用程序项目。
以下是从现实世界问题中更详细地处理的 case1 和 case2。
评论
链接的 .lib 文件与 .dll 相关联
我有同样的问题。假设我有项目 MyProject 和 TestProject。我已经有效地将 MyProject 的 lib 文件链接到了 TestProject。但是,此 lib 文件是在生成 MyProject 的 DLL 时生成的。此外,我没有包含 MyProject 中所有方法的源代码,而只包含对 DLL 入口点的访问。
为了解决这个问题,我将 MyProject 构建为 LIB,并将 TestProject 链接到此 .lib 文件(我将生成的 .lib 文件复制粘贴到 TestProject 文件夹中)。然后,我可以再次将 MyProject 构建为 DLL。它正在编译,因为 TestProject 链接到的库确实包含 MyProject 中类中所有方法的代码。
评论
此外,如果您使用的是第三方库,请确保您拥有正确的 32/64 位二进制文件
指定相互依赖的链接库的顺序是错误的。
如果库相互依赖,则链接库的顺序确实很重要。通常,如果 library 依赖于 library ,则 MUST 出现在链接器标志中。A
B
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
所以再重复一遍,顺序确实很重要!
评论
Microsoft 提供了在链接时引用正确库的功能;#pragma
#pragma comment(lib, "libname.lib")
除了库路径(包括库的目录)之外,这应该是库的全名。
什么是“未定义的引用/未解析的外部符号”
我将尝试解释什么是“未定义的引用/未解析的外部符号”。
注意:我使用 g++ 和 Linux,所有示例都是为了它
例如,我们有一些代码
// 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%d\n", global_var_name, local_var_name);
printf("%d\n", 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
链接器查看导出的符号并将其链接。现在我们尝试取消注释 src2.cpp 中的行,如下所示
// src2.cpp
extern "C" int printf (const char*, ...);
extern int global_var_name;
extern int local_var_name;
void print ()
{
printf("%d%d\n", global_var_name, local_var_name);
}
并重新生成对象文件
$ g++ -c src2.cpp -o src2.o
OK(没有错误),因为我们只构建对象文件,链接还没有完成。 尝试链接
$ 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是静态的,即它对其他模块不可见。 现在更深入。获取转换相位输出
$ 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 并更改
.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
即你应该有如下
.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绑定了 GLOBAL(原来是 LOCAL)
链接
$ g++ src1.o src2.o -o prog
并运行它
$ ./prog
123456789
好的,我们破解它:)
因此,结果是,当链接器在目标文件中找不到全局符号时,会发生“未定义的引用/未解析的外部符号错误”。
评论
编译器/IDE 中的错误
我最近遇到了这个问题,原来这是Visual Studio Express 2013中的一个错误。我不得不从项目中删除源文件并重新添加它以克服该错误。
如果您认为这可能是编译器/IDE 中的错误,请尝试以下步骤:
- 清理项目(某些 IDE 可以选择执行此操作,您也可以 通过删除目标文件手动执行此操作)
- 尝试开始一个新项目, 从原始代码复制所有源代码。
评论
Visual Studio NuGet 包需要针对新的工具集版本进行更新
我只是在尝试将libpng与Visual Studio 2013链接时遇到了这个问题。问题是包文件只有 Visual Studio 2010 和 2012 的库。
正确的解决方案是希望开发人员发布更新的软件包然后升级,但它通过入侵 VS2013 的额外设置,指向 VS2012 库文件对我有用。
我编辑了包(在解决方案目录内的文件夹中),方法是在该文件中查找并复制所有部分。我在条件字段中更改了 to,只是非常小心地将文件名路径全部保留为 .这只是允许 Visual Studio 2013 链接到 2012 年的库,在这种情况下,它起作用了。packages
packagename\build\native\packagename.targets
v110
v110
v120
v110
评论
不支持链接器脚本的 GNU ld 包装器
一些 .so 文件实际上是 GNU ld 链接器脚本,例如 libtbb.so 文件是包含以下内容的 ASCII 文本文件:
INPUT (libtbb.so.2)
一些更复杂的版本可能不支持此功能。例如,如果在编译器选项中包含 -v,则可以看到 mainwin gcc 包装器 mwdip 丢弃了要链接的库的详细输出列表中的链接器脚本命令文件。一个简单的解决方法是将链接器脚本输入命令文件替换为文件的副本(或符号链接),例如
cp libtbb.so.2 libtbb.so
或者你可以用 .so 的完整路径替换 -l 参数,例如,而不是 do-ltbb
/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2
这是每个 VC++ 程序员一次又一次看到的最令人困惑的错误消息之一。让我们先把事情弄清楚。
一个。什么是符号?简而言之,符号就是一个名称。它可以是变量名、函数名、类名、typedef 名或除属于 C++ 语言的名称和符号之外的任何名称。它是用户定义或由依赖库(另一个用户定义的)引入的。
湾。什么是外部的?在 VC++ 中,每个源文件(.cpp,.c 等)都被视为一个转换单元,编译器一次编译一个单元,并为当前转换单元生成一个目标文件(.obj)。(请注意,此源文件包含的每个头文件都将经过预处理,并将被视为此转换单元的一部分)翻译单元内的所有内容都被视为内部内容,其他所有内容都被视为外部内容。在 C++ 中,可以使用关键字(如 等)引用外部符号。extern
__declspec (dllimport)
三.什么是“决心”?Resolve是一个链接时间术语。在链接时,链接器尝试为无法在内部找到其定义的对象文件中的每个符号查找外部定义。此搜索过程的范围包括:
- 在编译时生成的所有目标文件
- 显式或隐式的所有库 (.lib) 指定为此生成应用程序的其他依赖项。
此搜索过程称为解析。
D.最后,为什么是未解析的外部符号?如果链接器找不到内部没有定义的符号的外部定义,则会报告“未解析的外部符号”错误。
E. LNK2019的可能原因:未解决的外部符号错误。 我们已经知道这个错误是由于链接器找不到外部符号的定义,可能的原因可以归纳为:
- 存在定义
例如,如果我们在 a.cpp 中定义了一个名为 foo 的函数:
int foo()
{
return 0;
}
在 b.cpp 中,我们想调用函数 foo,所以我们添加
void foo();
要声明函数 foo(),并在另一个函数体中调用它,请说:bar()
void bar()
{
foo();
}
现在,当您构建此代码时,您将收到一个LNK2019错误,抱怨 foo 是一个未解析的符号。在这种情况下,我们知道 foo() 在 .cpp 中有它的定义,但与我们调用的(不同的返回值)不同。这就是定义存在的情况。
- 定义不存在
如果我们想调用库中的某些函数,但导入库未添加到项目设置的附加依赖项列表(设置自:)中。现在,链接器将报告LNK2019,因为当前搜索范围内不存在该定义。Project | Properties | Configuration Properties | Linker | Input | Additional Dependency
使用链接器帮助诊断错误
大多数现代链接器都包含一个详细选项,该选项可在不同程度上打印出来;
- 链接调用(命令行),
- 有关链接阶段包含哪些库的数据,
- 图书馆的位置,
- 使用的搜索路径。
对于 gcc 和 clang;通常会将 或 添加到命令行中。更多细节可以在这里找到;-v -Wl,--verbose
-v -Wl,-v
对于 MSVC,(特别是 )将添加到链接命令行中。/VERBOSE
/VERBOSE:LIB
/VERBOSE
链接器选项上的 MSDN 页。
假设你有一个用 c++ 编写的大项目,它有一千个 .cpp 文件和一千个 .h 文件。假设该项目还依赖于十个静态库。假设我们在 Windows 上,并在 Visual Studio 20xx 中构建项目。当您按 Ctrl + F7 Visual Studio 开始编译整个解决方案时(假设解决方案中只有一个项目)
编译是什么意思?
- Visual Studio 搜索文件.vcxproj,并开始编译扩展名为 .cpp 的每个文件。编译顺序未定义。因此,您不能假设首先编译文件 main.cpp
- 如果.cpp文件依赖于其他 .h 文件来查找符号 可能在文件.cpp中定义,也可能不定义
- 如果存在一个编译器找不到一个符号的 .cpp 文件,则编译器时间错误会引发消息“找不到符号 x
- 对于扩展名为 .cpp 的每个文件,都会生成一个对象文件 .o,并且 Visual Studio 还会将输出写入名为 ProjectName.Cpp.Clean.txt 的文件中,该文件包含必须由链接器处理的所有对象文件。
编译的第二步由 Linker 完成,Linker 应该合并所有目标文件,并最终构建输出(可以是可执行文件或库)
链接项目的步骤
- 解析所有对象文件并找到仅在标头中声明的定义(例如:前面的答案中提到的类的一个方法的代码,或者事件是作为类内部成员的静态变量的初始化)
- 如果在目标文件中找不到一个符号,则还会在“其他库”中搜索该符号。要向项目添加新库,请执行以下操作:配置属性 -> VC++ 目录 -> 库目录,并在此处指定了用于搜索库的附加文件夹和配置属性 -> 链接器 -> 输入用于指定库的名称。
- 如果链接器找不到您在一个 .cpp 中写入的符号,他会引发链接器时间错误,这听起来可能像
error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
观察
- 一旦链接器找到一个符号,他就不会在其他库中搜索它
- 链接库的顺序确实很重要。
- 如果链接器在一个静态库中找到外部符号,则会在项目的输出中包含该符号。但是,如果库是共享的( 动态 ),则他不会在输出中包含代码(符号),但可能会发生运行时崩溃
如何解决这种错误
编译器时间错误:
- 确保编写 c++ 项目语法正确。
链接器时间错误
- 定义您在头文件中声明的所有符号
- 用于允许编译器不包含一个标头(如果该标头已包含在已编译的当前.cpp中)
#pragma once
- 确保外部库不包含可能与头文件中定义的其他符号发生冲突的符号
- 使用模板时,请确保在头文件中包含每个模板函数的定义,以允许编译器为任何实例化生成适当的代码。
评论
由于当涉及到链接器错误时,人们似乎会被引导到这个问题,我将在这里添加这个问题。
GCC 5.2.0 中链接器错误的一个可能原因是现在默认选择了新的 libstdc++ 库 ABI。
如果收到有关对涉及 std::__cxx11 命名空间或标记 [abi:cxx11] 中的类型的符号的未定义引用的链接器错误,则可能表明您正在尝试将使用 _GLIBCXX_USE_CXX11_ABI 宏的不同值编译的对象文件链接在一起。当链接到使用旧版本的 GCC 编译的第三方库时,通常会发生这种情况。如果无法使用新的 ABI 重建第三方库,则需要使用旧的 ABI 重新编译代码。
因此,如果您在 5.1.0 之后切换到 GCC 时突然出现链接器错误,这将是一件值得检查的事情。
清洁和重建
对构建进行“清理”可以删除可能残留在以前的构建、失败构建、不完整构建和其他与构建系统相关的构建问题中的“枯木”。
通常,IDE 或构建将包含某种形式的“干净”函数,但这可能无法正确配置(例如在手动生成文件中)或可能失败(例如,中间或生成的二进制文件是只读的)。
“清理”完成后,验证“清理”是否成功,以及所有生成的中间文件(例如自动生成文件)是否已成功删除。
这个过程可以看作是最后的手段,但往往是一个好的第一步;特别是如果最近添加了与错误相关的代码(本地或来自源存储库)。
与模板交朋友...
给定带有友元运算符(或函数)的模板类型的代码片段;
template <typename T>
class Foo {
friend std::ostream& operator<< (std::ostream& os, const Foo<T>& a);
};
被声明为非模板函数。对于与 一起使用的每种类型,都需要有一个非模板化的 .例如,如果声明了类型,则必须有如下运算符实现;operator<<
T
Foo
operator<<
Foo<int>
std::ostream& operator<< (std::ostream& os, const Foo<int>& a) {/*...*/}
由于未实现,链接器无法找到它并导致错误。
若要更正此问题,可以在类型之前声明一个模板运算符,然后将相应的实例化声明为友元。语法有点笨拙,但如下所示;Foo
// forward declare the Foo
template <typename>
class Foo;
// forward declare the operator <<
template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&);
template <typename T>
class Foo {
friend std::ostream& operator<< <>(std::ostream& os, const Foo<T>& a);
// note the required <> ^^^^
// ...
};
template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&)
{
// ... implement the operator
}
上面的代码将算子的友情限制在 的相应实例化上,即实例化仅限于访问 的实例化的私有成员。Foo
operator<< <int>
Foo<int>
替代方案包括;
允许友谊扩展到模板的所有实例,如下所示;
template <typename T> class Foo { template <typename T1> friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a); // ... };
或者,可以在类定义中以内联方式完成实现;
operator<<
template <typename T> class Foo { friend std::ostream& operator<<(std::ostream& os, const Foo& a) { /*...*/ } // ... };
请注意,当运算符(或函数)的声明仅出现在类中时,该名称不可用于“正常”查找,仅可用于 cppreference 中的参数相关查找;
在类或类模板 X 中的友元声明中首次声明的名称将成为 X 最内层封闭命名空间的成员,但无法进行查找(考虑 X 的参数相关查找除外),除非在命名空间范围内提供匹配的声明......
在 cppreference 和 C++ FAQ 上有关于模板朋友的进一步阅读。
作为失败代码示例的旁注;G++ 对此发出如下警告
warning: friend declaration 'std::ostream& operator<<(...)' declares a non-template function [-Wnon-template-friend]
note: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here)
定义不一致UNICODE
Windows UNICODE 版本是使用 etc. 定义为 etc 构建的。当未使用 defined as 构建 build with defined as 等时。这些和定义会影响所有“T
”字符串类型;和他们的麋鹿。TCHAR
wchar_t
UNICODE
TCHAR
char
UNICODE
_UNICODE
LPTSTR
LPCTSTR
构建一个已定义的库并尝试将其链接到未定义的项目中将导致链接器错误,因为 的定义将不匹配; 与。。UNICODE
UNICODE
TCHAR
char
wchar_t
错误通常包括一个函数、一个具有 或派生类型的值,这些也可能包括等。在浏览代码中受影响的函数时,通常会引用 or 等。这是一个明显的迹象,表明该代码最初用于 UNICODE 和多字节字符(或“窄”)构建。char
wchar_t
std::basic_string<>
TCHAR
std::basic_string<TCHAR>
要更正此问题,请使用一致的定义 (和 ) 构建所有必需的库和项目。UNICODE
_UNICODE
这可以通过以下任何一种方式完成;
#define UNICODE #define _UNICODE
或在项目设置中;
常规>项目属性>项目默认值>字符集
或者在命令行上;
/DUNICODE /D_UNICODE
如果不打算使用 UNICODE,请确保未设置定义,和/或在项目中使用多字符设置并一致地应用,则替代方法也适用。
不要忘记在“发布”和“调试”版本之间保持一致。
当包含路径不同时
当头文件及其关联的共享库(.lib 文件)不同步时,可能会发生链接器错误。让我解释一下。
链接器如何工作?链接器通过比较函数声明的签名,将函数声明(在标头中声明)与其定义(在共享库中)进行匹配。如果链接器找不到完全匹配的函数定义,则可能会出现链接器错误。
即使声明和定义看起来匹配,是否仍然可能出现链接器错误?是的!它们在源代码中可能看起来相同,但这实际上取决于编译器看到的内容。从本质上讲,您最终可能会遇到这样的情况:
// header1.h
typedef int Number;
void foo(Number);
// header2.h
typedef float Number;
void foo(Number); // this only looks the same lexically
请注意,尽管这两个函数声明在源代码中看起来相同,但根据编译器的不同,它们实际上是不同的。
你可能会问,一个人怎么会陷入这样的境地?当然包括路径!如果在编译共享库时,包含路径通向并且您最终在自己的程序中使用,那么您将不知道发生了什么(双关语)。header1.h
header2.h
下面将解释这在现实世界中如何发生的一个例子。
举例进一步阐述
我有两个项目:和.这两个项目都依赖于 .假设库导出以下函数:graphics.lib
main.exe
common_math.h
// graphics.lib
#include "common_math.h"
void draw(vec3 p) { ... } // vec3 comes from common_math.h
然后,您继续将该库包含在您自己的项目中。
// main.exe
#include "other/common_math.h"
#include "graphics.h"
int main() {
draw(...);
}
繁荣!你收到一个链接器错误,你不知道它为什么会失败。原因是公共库使用同一包含的不同版本(我在示例中通过包含不同的路径来表明这一点,但它可能并不总是那么明显。也许编译器设置中的包含路径不同)。common_math.h
请注意,在此示例中,链接器会告诉您它找不到,而实际上您知道它显然正在由库导出。你可能会花几个小时挠头,想知道出了什么问题。问题是,链接器看到的签名不同,因为参数类型略有不同。在此示例中,就编译器而言,是两个项目中的不同类型。这可能是因为它们来自两个略有不同的包含文件(可能包含文件来自两个不同版本的库)。draw()
vec3
调试链接器
如果你使用的是 Visual Studio,则 DUMPBIN 是你的朋友。我敢肯定其他编译器还有其他类似的工具。
过程是这样的:
- 请注意链接器错误中给出的奇怪的损坏名称。(例如draw@graphics@XYZ)。
- 将导出的符号从库转储到文本文件中。
- 搜索导出的感兴趣符号,并注意损坏的名称不同。
- 注意为什么被破坏的名字最终会有所不同。您将能够看到参数类型不同,即使它们在源代码中看起来相同。
- 它们不同的原因。在上面给出的示例中,由于包含文件不同,它们有所不同。
[1] 我所说的项目是指一组源文件,这些源文件链接在一起以生成库或可执行文件。
编辑1:重写了第一部分,使其更易于理解。请在下面发表评论,让我知道是否还有其他需要修复的问题。谢谢!
链接在引用库的目标文件之前使用库
- 您正在尝试编译您的程序并将其与 GCC 工具链链接。
- 您的链接指定了所有必需的库和库搜索路径
- 如果依赖于 ,则您的链接正确地放在 之前。
libfoo
libbar
libfoo
libbar
- 您的链接因错误而失败。
undefined reference to
- 但是所有未定义的东西都是在你拥有的头文件中声明的,并且实际上是在你链接的库中定义的。
#include
示例在 C 语言中。它们同样可能是 C++
一个涉及您自己构建的静态库的最小示例
my_lib.c
#include "my_lib.h"
#include <stdio.h>
void hw(void)
{
puts("Hello World");
}
my_lib.h
#ifndef MY_LIB_H
#define MT_LIB_H
extern void hw(void);
#endif
eg1.c网站
#include <my_lib.h>
int main()
{
hw();
return 0;
}
构建静态库:
$ gcc -c -o my_lib.o my_lib.c
$ ar rcs libmy_lib.a my_lib.o
你编译你的程序:
$ gcc -I. -c -o eg1.o eg1.c
您尝试将其与以下链接链接失败:libmy_lib.a
$ gcc -o eg1 -L. -lmy_lib eg1.o
eg1.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status
如果你在一个步骤中编译和链接,则结果相同,例如:
$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
/tmp/ccQk1tvs.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status
一个涉及共享系统库(压缩库)的最小示例libz
eg2.c的
#include <zlib.h>
#include <stdio.h>
int main()
{
printf("%s\n",zlibVersion());
return 0;
}
编译程序:
$ gcc -c -o eg2.o eg2.c
尝试将您的程序与以下链接并失败:libz
$ gcc -o eg2 -lz eg2.o
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status
如果您一次性编译和链接,则相同:
$ gcc -o eg2 -I. -lz eg2.c
/tmp/ccxCiGn7.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status
以及示例 2 的变体,涉及:pkg-config
$ gcc -o eg2 $(pkg-config --libs zlib) eg2.o
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
你做错了什么?
在要链接的对象文件和库的序列中,使 程序中,您将库放在引用 他们。您需要将库放在引用 对他们来说。
正确链接示例 1:
$ gcc -o eg1 eg1.o -L. -lmy_lib
成功:
$ ./eg1
Hello World
正确链接示例 2:
$ gcc -o eg2 eg2.o -lz
成功:
$ ./eg2
1.2.8
正确链接示例 2 变体:pkg-config
$ gcc -o eg2 eg2.o $(pkg-config --libs zlib)
$ ./eg2
1.2.8
解释
从现在开始,阅读是可选的。
默认情况下,GCC 生成的链接命令在您的发行版上, 从左到右使用链接中的文件 命令行序列。当它发现一个文件引用了某物并且不包含它的定义时,将搜索一个定义 在更右边的文件中。如果它最终找到一个定义,则 引用已解析。如果任何引用在最后仍未解决, 链接失败:链接器不向后搜索。
首先,示例 1,使用静态库my_lib.a
静态库是目标文件的索引存档。当链接器
在链接序列中发现并找出这指的是
到静态库,它想知道你的程序是否
需要 中的任何对象文件。-lmy_lib
./libmy_lib.a
libmy_lib.a
中只有 目标文件,即 ,并且只定义了一件事
中,即函数。libmy_lib.a
my_lib.o
my_lib.o
hw
链接器将决定您的程序是否需要,当且仅当它已经知道时
您的程序在它已有的一个或多个目标文件中引用
添加到程序中,并且没有添加任何对象文件
包含 的定义。my_lib.o
hw
hw
如果为 true,则链接器将从库中提取
将其添加到您的程序中。然后,您的程序包含 的定义,因此
其对 的引用已解析。my_lib.o
hw
hw
当您尝试链接程序时,例如:
$ gcc -o eg1 -L. -lmy_lib eg1.o
链接器在看到 时尚未添加到程序中。因为在那个时候,它还没有看到.
您的程序尚未对 : it 进行任何引用
根本没有做任何引用,因为它所做的所有引用
在 .eg1.o
-lmy_lib
eg1.o
hw
eg1.o
因此,链接器不会添加到程序中,也没有进一步的
用于 .my_lib.o
libmy_lib.a
接下来,它找到 ,并将其添加到程序中。中的对象文件
联动序列始终添加到程序中。现在,该程序使
对 的引用,但不包含 的定义 ;但
联动序列中没有任何东西可以提供缺失的东西
定义。对 的引用最终无法解析,并且链接失败。eg1.o
hw
hw
hw
第二,示例 2,使用共享库libz
共享库不是对象文件或类似文件的存档。它
更像是一个没有功能的程序,并且
而是公开它定义的多个其他符号,以便其他
程序可以在运行时使用它们。main
如今,许多 Linux 发行版都配置了他们的 GCC 工具链,以便其语言驱动程序(、等)
指示系统链接器 () 根据需要链接共享库。
你已经有了其中一个发行版。gcc
g++
gfortran
ld
这意味着,当链接器在链接序列中找到,并发现这指的是
对于共享库(例如),它想知道它添加到程序中但尚未定义的任何引用是否具有由-lz
/usr/lib/x86_64-linux-gnu/libz.so
libz
如果为 true,则链接器不会从 和 中复制任何块
将它们添加到您的程序中;相反,它只会修改程序的代码
因此:-libz
在运行时,系统程序加载器会将 每当程序加载程序的副本时,运行它的过程与程序相同。
libz
在运行时,每当程序引用 中定义的内容时,该引用都会使用 中 的副本导出的定义 同样的过程。
libz
libz
您的程序只想引用一个定义由 导出的事物,由
即函数,在 中只被引用一次。
如果链接器将该引用添加到程序中,然后找到定义
导出者 ,引用被解析libz
zlibVersion
eg2.c
libz
但是当您尝试链接程序时,例如:
gcc -o eg2 -lz eg2.o
事件的顺序是错误的,与示例 1 相同。
在链接器找到 时,没有对任何内容的引用
在节目中:他们都在,这还没有被看到。所以
链接器认为它对 没有用处。当它到达时,将其添加到程序中,
然后有未定义的引用,联动序列完成;
该引用未解析,链接失败。-lz
eg2.o
libz
eg2.o
zlibVersion
最后,示例 2 的变体现在有一个显而易见的解释。
shell-expansion 后:pkg-config
gcc -o eg2 $(pkg-config --libs zlib) eg2.o
成为:
gcc -o eg2 -lz eg2.o
这又只是示例 2。
我可以在示例 1 中重现该问题,但不能在示例 2 中重现该问题
联动:
gcc -o eg2 -lz eg2.o
对你来说很好!
(或者:该链接在 Fedora 23 上工作正常,但在 Ubuntu 16.04 上失败)
那是因为链接工作的发行版是其中之一 未将其 GCC 工具链配置为根据需要链接共享库。
过去,类 unix 系统将静态和共享链接起来是很正常的 库采用不同的规则。链接序列中的静态库 在示例 1 中解释的按需基础上,但共享库是无条件链接的。
这种行为在链接时是经济的,因为链接者不必考虑 程序是否需要共享库:如果它是共享库, 链接它。大多数链接中的大多数库都是共享库。但也有缺点:-
这在运行时是不经济的,因为它可能导致共享库 即使不需要它们,也与程序一起加载。
静态库和共享库的不同链接规则可能会令人困惑 对于不专业的程序员来说,他们可能不知道是否在他们的链接中 将解析为 或 , 并且可能不了解共享库和静态库之间的区别 无论如何。
-lfoo
/some/where/libfoo.a
/some/where/libfoo.so
这种权衡导致了今天的分裂局面。一些发行版有 更改了共享库的 GCC 链接规则,以便按需原则适用于所有库。一些发行版坚持使用旧的 道路。
为什么即使我同时编译和链接,我仍然会遇到这个问题?
如果我只是这样做:
$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
当然,GCC 必须首先编译,然后链接结果
带有 的对象文件。那么它怎么可能不知道那个目标文件
在进行链接时需要吗?eg1.c
libmy_lib.a
因为使用单个命令进行编译和链接不会改变 链接序列的顺序。
当你运行上面的命令时,弄清楚你需要编译 +
联动。因此,在后台,它会生成一个编译命令,并运行
它,然后生成一个链接命令,并运行它,就像你已经运行
两个命令:gcc
$ gcc -I. -c -o eg1.o eg1.c
$ gcc -o eg1 -L. -lmy_lib eg1.o
因此,链接会失败,就像运行这两个命令一样。这
您在失败中注意到的唯一区别是 GCC 生成了一个
编译 + 链接情况下的临时对象文件,因为您没有告诉它
要使用 .我们看到:eg1.o
/tmp/ccQk1tvs.o: In function `main'
而不是:
eg1.o: In function `main':
另请参阅
将相互依赖的库放在错误的顺序中只是一种方法 您可以在其中获取需要定义即将到来的事物的文件 在链接中比提供定义的文件晚。将库放在 引用它们的目标文件是犯相同错误的另一种方式。
变量声明/定义中缺少“extern”(仅限 C++)const
对于来自C的人来说,在C++中全局变量具有内部(或静态)链接可能会令人惊讶。在 C 语言中,情况并非如此,因为所有全局变量都是隐式的(即当缺少关键字时)。const
extern
static
例:
// file1.cpp
const int test = 5; // in C++ same as "static const int test = 5"
int test2 = 5;
// file2.cpp
extern const int test;
extern int test2;
void foo()
{
int x = test; // linker error in C++ , no error in C
int y = test2; // no problem
}
正确的做法是使用头文件并将其包含在 file2.cpp 和 file1.cpp 中
extern const int test;
extern int test2;
或者,可以在 file1.cpp 中声明变量,并显式const
extern
尽管这是一个相当古老的问题,有多个公认的答案,但我想分享如何解决一个晦涩难懂的“未定义引用”错误。
不同版本的库
我使用别名来指代:文件系统自 C++17 以来就在标准库中,但我的程序也需要在 C++14 中编译,所以我决定使用变量别名:std::filesystem::path
#if (defined _GLIBCXX_EXPERIMENTAL_FILESYSTEM) //is the included filesystem library experimental? (C++14 and newer: <experimental/filesystem>)
using path_t = std::experimental::filesystem::path;
#elif (defined _GLIBCXX_FILESYSTEM) //not experimental (C++17 and newer: <filesystem>)
using path_t = std::filesystem::path;
#endif
假设我有三个文件:main.cpp、file.h、file.cpp:
- file.h #include 的<experimental::filesystem>并包含上面的代码
- file.cpp,file.h 的实现,#include 的“file.h"
- main.cpp #include 的<文件系统>和“file.h"
请注意 main.cpp 和 file.h 中使用的不同库。由于 main.cpp #include 在 <filesystem> 之后加上 “file.h”,因此那里使用的文件系统版本是 C++17 版本。我曾经使用以下命令编译程序:
$ g++ -g -std=c++17 -c main.cpp
-> 将 main.cpp 编译为 main.o $ -> 将 file.cpp 和 file.h 编译为 file.o $ ->链接 main.o 和 file.og++ -g -std=c++17 -c file.cpp
g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs
这样,file.o 中包含的和 main.o 中使用的任何需要 path_t
的函数都会出现“未定义的引用”错误,因为 main.o 引用了 std::filesystem::p ath,但 file.o 引用了 std::experimental::filesystem::p
ath
。
分辨率
要解决这个问题,我只需要将 file.h 中的 <experimental::filesystem>更改为 <filesystem>。
链接到共享库时,请确保未隐藏使用的符号。
gcc 的默认行为是所有符号都是可见的。但是,当使用 option 构建翻译单元时,在生成的共享对象中,只有标有 的函数/符号是外部的。-fvisibility=hidden
__attribute__ ((visibility ("default")))
您可以通过调用以下命令来检查您要查找的符号是否是外部符号:
# -D shows (global) dynamic symbols that can be used from the outside of XXX.so
nm -D XXX.so | grep MY_SYMBOL
隐藏/本地符号以小写符号类型显示,例如,代码段代替“T”:nm
t
nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL
您还可以使用选项来解解名称(如果使用了 C++)。nm
-C
与 Windows-dll 类似,人们会用定义来标记公共函数,例如定义为:DLL_PUBLIC
#define DLL_PUBLIC __attribute__ ((visibility ("default")))
DLL_PUBLIC int my_public_function(){
...
}
大致对应于 Windows 的/MSVC 版本:
#ifdef BUILDING_DLL
#define DLL_PUBLIC __declspec(dllexport)
#else
#define DLL_PUBLIC __declspec(dllimport)
#endif
有关可见性的更多信息,请访问 gcc wiki。
当使用生成的符号编译翻译单元时,仍然具有外部链接(以大写符号类型显示),并且如果目标文件成为静态库的一部分,则可以毫无问题地用于外部链接。仅当目标文件链接到共享库时,链接才成为本地链接。-fvisibility=hidden
nm
若要查找对象文件中的哪些符号是隐藏的,请运行:
>>> objdump -t XXXX.o | grep hidden
0000000000000000 g F .text 000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g F .text 000000000000000b .hidden HIDDEN_SYMBOL2
不同的架构
您可能会看到如下消息:
library machine type 'x64' conflicts with target machine type 'X86'
在这种情况下,这意味着可用符号的体系结构与要编译的体系结构不同。
在 Visual Studio 上,这是由于错误的“平台”造成的,您需要选择正确的平台或安装正确版本的库。
在 Linux 上,这可能是由于错误的库文件夹(例如,使用 而不是)。lib
lib64
在 MacOS 上,可以选择在同一文件中提供两种架构。该链接可能期望两个版本都存在,但只有一个版本存在。也可能是选取库的错误/文件夹的问题。lib
lib64
函数或类方法在源文件中使用说明符定义。inline
举个例子:-
main.cpp
#include "gum.h"
#include "foo.h"
int main()
{
gum();
foo f;
f.bar();
return 0;
}
foo.h (1)
#pragma once
struct foo {
void bar() const;
};
口香糖 (1)
#pragma once
extern void gum();
foo.cpp (1)
#include "foo.h"
#include <iostream>
inline /* <- wrong! */ void foo::bar() const {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
口香糖.cpp (1)
#include "gum.h"
#include <iostream>
inline /* <- wrong! */ void gum()
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
如果指定 (类似地, ) 是其定义,则
编译器将通过以下方式内联(如果它选择):-gum
foo::bar
inline
gum
- 不发出任何唯一的定义,因此
gum
- 不发出链接器可以引用 定义的任何符号,而是
gum
- 将所有调用替换为 的已编译主体的内联副本。
gum
gum
因此,如果在源文件中定义内联,则它是
编译为一个对象文件,其中所有对 的调用都内联在其中
并且没有定义链接器可以引用的符号。当你
与另一个目标文件一起链接到一个程序中,例如 引用外部符号时,链接器无法解析
这些参考资料。所以联动失败:gum
gum.cpp
gum.o
gum
gum
gum.o
main.o
gum
编译:
g++ -c main.cpp foo.cpp gum.cpp
链接:
$ g++ -o prog main.o foo.o gum.o
main.o: In function `main':
main.cpp:(.text+0x18): undefined reference to `gum()'
main.cpp:(.text+0x24): undefined reference to `foo::bar() const'
collect2: error: ld returned 1 exit status
您只能进行定义,就好像编译器可以在每个可能被调用的源文件中看到其定义一样。这意味着它的内联定义需要存在于每个源文件中包含的头文件中
你编译其中可以调用。执行以下两项操作之一:gum
inline
gum
gum
要么不内联定义
从源文件定义中删除说明符:inline
FOO.cpp (2)
#include "foo.h"
#include <iostream>
void foo::bar() const {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
口香糖.cpp (2)
#include "gum.h"
#include <iostream>
void gum()
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
用它重建:
$ g++ -c main.cpp foo.cpp gum.cpp
imk@imk-Inspiron-7559:~/develop/so/scrap1$ g++ -o prog main.o foo.o gum.o
imk@imk-Inspiron-7559:~/develop/so/scrap1$ ./prog
void gum()
void foo::bar() const
成功。
或正确内联
头文件中的内联定义:
foo.h (2)
#pragma once
#include <iostream>
struct foo {
void bar() const { // In-class definition is implicitly inline
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
};
// Alternatively...
#if 0
struct foo {
void bar() const;
};
inline void foo::bar() const {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
#endif
口香糖 (2)
#pragma once
#include <iostream>
inline void gum() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
现在我们不需要或:foo.cpp
gum.cpp
$ g++ -c main.cpp
$ g++ -o prog main.o
$ ./prog
void gum()
void foo::bar() const
评论
inline
inline
通过在头文件中声明函数的原型,我遇到了这个问题:
int createBackground(VertexArray rVA, IntRect arena);
但是,然后使用带有第一个参数的引用在其他地方定义函数:
int createBackground(VertexArray& rVA, IntRect arena) {}
原型在第一个参数中没有使用引用,而定义是引用,这一事实导致了这个问题。当我将两者更改为正确匹配包含引用或不包含引用时,问题已得到解决。
干杯。
我的例子:
头文件
class GameCharacter : public GamePart
{
private:
static vector<GameCharacter*> characterList;
...
}
.cpp 文件:
vector<GameCharacter*> characterList;
这会产生“未定义”加载器错误,因为“characterList”被声明为静态成员变量,但被定义为全局变量。
我之所以添加这个,是因为 -- 虽然其他人把这个案例列在一长串需要注意的事项中 -- 但这个列表没有给出例子。这是一个需要寻找的更多内容的例子,尤其是在 C++ 中。
解决方法是向全局变量添加限定条件以定义静态数据成员:
vector<GameCharacter*> GameCharacter::characterList;
同时保持标题相同。
评论
当我们在程序中引用对象名称(类、函数、变量等)并且链接器尝试在所有链接的对象文件和库中搜索它时找不到它的定义时,就会发生“未定义的引用”错误。
因此,当链接器找不到链接对象的定义时,它会发出“未定义的引用”错误。从定义中可以清楚地看出,此错误发生在链接过程的后期阶段。导致“未定义引用”错误的原因有很多。
一些可能的原因(更常见):
#1) 没有为对象提供定义
这是导致“未定义引用”错误的最简单原因。程序员只是忘记了定义对象。
请考虑以下 C++ 程序。在这里,我们只指定了函数的原型,然后在主函数中使用它。
#include <iostream>
int func1();
int main()
{
func1();
}
输出:
main.cpp:(.text+0x5): undefined reference to 'func1()'
collect2: error ld returned 1 exit status
因此,当我们编译此程序时,会发出链接器错误,指出“对'func1()'的未定义引用”。
为了消除这个错误,我们通过提供函数 func1 的定义来更正程序,如下所示。现在程序给出适当的输出。
#include <iostream>
using namespace std;
int func1();
int main()
{
func1();
}
int func1(){
cout<<"hello, world!!";
}
输出:
hello, world!!
#2) 使用对象的定义错误(签名不匹配)
“未定义引用”错误的另一个原因是当我们指定错误的定义时。我们在程序中使用任何对象,其定义是不同的。
请考虑以下 C++ 程序。在这里,我们调用了 func1 ()。它的原型是 int func1()。但它的定义与其原型不符。正如我们所看到的,函数的定义包含函数的参数。
因此,当程序编译时,由于原型和函数调用匹配,编译成功。但是,当链接器尝试将函数调用与其定义链接时,它会发现问题并将错误作为“未定义的引用”发出。
#include <iostream>
using namespace std;
int func1();
int main()
{
func1();
}
int func1(int n){
cout<<"hello, world!!";
}
输出:
main.cpp:(.text+0x5): undefined reference to 'func1()'
collect2: error ld returned 1 exit status
因此,为了防止此类错误,我们只需交叉检查所有对象的定义和用法是否在我们的程序中匹配。
#3) 对象文件未正确链接
此问题还可能导致“未定义的引用”错误。在这里,我们可能有多个源文件,我们可以独立编译它们。完成此操作后,对象未正确链接,并导致“未定义的引用”。
请考虑以下两个 C++ 程序。在第一个文件中,我们使用第二个文件中定义的“print()”函数。当我们单独编译这些文件时,第一个文件为 print 函数提供“未定义的引用”,而第二个文件为 main 函数提供“未定义的引用”。
int print();
int main()
{
print();
}
输出:
main.cpp:(.text+0x5): undefined reference to 'print()'
collect2: error ld returned 1 exit status
int print() {
return 42;
}
输出:
(.text+0x20): undefined reference to 'main'
collect2: error ld returned 1 exit status
解决此错误的方法是同时编译这两个文件(例如,使用 g++)。
除了已经讨论过的原因外,由于以下原因,还可能出现“未定义的参考”。
#4) 错误的项目类型
当我们在 C++ IDE(如 Visual Studio)中指定错误的项目类型并尝试执行项目不希望做的事情时,我们会得到“未定义的引用”。
#5) 没有库
如果程序员没有正确指定库路径或完全忘记指定它,那么我们就会从库中获得程序使用的所有引用的“未定义引用”。
#6) 依赖文件未编译
程序员必须确保我们事先编译项目的所有依赖项,以便在我们编译项目时,编译器找到所有依赖项并成功编译。如果缺少任何依赖项,则编译器会给出“未定义的引用”。
除了上面讨论的原因外,在许多其他情况下还可能发生“未定义引用”错误。但底线是程序员把事情弄错了,为了防止这个错误,应该纠正它们。
就我而言,语法是正确的,但是当一个类调用同一DLL中的第二个类时,我遇到了该错误。第二个类的 CPP 文件在 Visual Studio 中出错,在我的情况下,它被设置为而不是正确的文件,因此编译器在构建 CPP 文件时没有编译它并导致错误LNK2019Properties->Item Type
C/C++ Header
C/C++ compiler
下面是一个示例,假设语法正确,您应该通过更改属性中的项类型来获得错误
//class A header file
class ClassB; // FORWARD DECLERATION
class ClassA
{
public:
ClassB* bObj;
ClassA(HINSTANCE hDLL) ;
// member functions
}
--------------------------------
//class A cpp file
ClassA::ClassA(HINSTANCE hDLL)
{
bObj = new ClassB();// LNK2019 occures here
bObj ->somefunction();// LNK2019 occures here
}
/*************************/
//classB Header file
struct mystruct{}
class ClassB{
public:
ClassB();
mystruct somefunction();
}
------------------------------
//classB cpp file
/* This is the file with the wrong property item type in visual studio --C/C++ Header-*/
ClassB::somefunction(){}
ClassB::ClassB(){}
当您使用错误的编译器来构建程序时
如果您使用的是编译器套件,则应根据所使用的语言使用正确的编译器驱动程序。使用 或 编译和链接 C++ 程序。改用 or 将导致对 C++ 标准库符号的引用未定义。例:gcc
clang
g++
clang++
gcc
clang
$ gcc -o test test.cpp
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/bin/ld: /tmp/ccPv7MvI.o: warning: relocation against `_ZSt4cout' in read-only section `.text'
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/bin/ld: /tmp/ccPv7MvI.o: in function `main': test.cpp:(.text+0xe): undefined reference to `std::cout'
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/bin/ld: test.cpp:(.text+0x13): undefined reference to `std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)'
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/bin/ld: /tmp/ccPv7MvI.o: in function `__static_initialization_and_destruction_0(int, int)':
test.cpp:(.text+0x43): undefined reference to `std::ios_base::Init::Init()'
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/bin/ld: test.cpp:(.text+0x58): undefined reference to `std::ios_base::Init::~Init()'
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/bin/ld: warning: creating DT_TEXTREL in a PIE
collect2: error: ld returned 1 exit status
需要考虑的一些错别字错误:(作为初学者,我经常发生)
- 如果您使用的是类:检查您是否没有在定义函数的 cpp 文件中的函数名称之前忘记“classname::”。
- 如果使用正向声明:请确保声明正确的类型。例如:如果要转发声明“结构”,请使用“struct”而不是“class”。
评论
struct
class
struct
class
struct
class
将 Visual Studio Code 与代码运行程序扩展和多个 .c 或 .cpp 文件一起使用
附带的 Code Runner 仅适用于具有单个源文件的已编译程序。它不是为与多个源文件一起使用而设计的。您应该使用不同的扩展名,例如 C/C++ Makefile 项目扩展或 CMake 工具扩展,或者修复 Code Runner 扩展以使用多个文件,或者只是手动编辑 .json 配置文件。
我正在构建一个共享/动态库。它适用于 Linux 和 *BSD,但在 Mac OS X 上,完全相同的编译和链接命令会产生未解决的引用错误。什么给了?
Mac OS X 在内部与 Linux 和 *BSD 有很大不同。对象/可执行文件格式为
在 Linux 和 *BSD 上,在构建共享库时,默认情况下允许未解析的引用。期望在加载时,它们将针对(未知的)主可执行文件和/或其他共享库得到满足。如果在加载时无法解析这些符号,则共享库将无法加载。
在 Mac OS X 上,构建动态库时,默认情况下不允许未解析的引用。如果希望在加载时解析引用,则需要显式启用未解析的引用。这是通过链接器标志完成的。-undefined dynamic_lookup
在构建可加载的插件时,允许未解析的引用很有用。
上一个:Qt6 函数未定义
下一个:为什么模板只能在头文件中实现?
评论
this