什么是未定义的参考/未解析的外部符号错误?常见原因是什么?如何解决/预防?
当前回答
因为当涉及到链接器错误时,人们似乎都在关注这个问题,所以我将在这里添加这个问题。
GCC 5.2.0中出现链接器错误的一个可能原因是现在默认选择了新的libstdc++库ABI。
如果您在std::__cxx11命名空间或标记[abi:cx11]中获得了有关符号未定义引用的链接器错误,则可能表明您正在尝试将使用_GLIBCXX_USE_cxx11_abi宏的不同值编译的对象文件链接在一起。这通常发生在链接到使用旧版本GCC编译的第三方库时。如果无法使用新的ABI重建第三方库,则需要使用旧的ABI重新编译代码。
因此,如果在5.1.0之后切换到GCC时突然出现链接器错误,这将是一件值得检查的事情。
其他回答
编译器/IDE中的错误
我最近遇到了这个问题,结果发现这是Visual Studio Express 2013中的一个错误。我不得不从项目中删除一个源文件,然后重新添加它以克服错误。
如果您认为这可能是编译器/IDE中的错误,请尝试以下步骤:
清理项目(一些IDE可以选择这样做,您也可以手动删除对象文件)尝试启动新项目,从原始代码复制所有源代码。
假设您有一个用c++编写的大型项目,它有一千个.cpp文件和一千个.h文件。假设该项目还依赖于十个静态库。假设我们在Windows上,我们在Visual Studio 20xx中构建项目。当您按Ctrl+F7 Visual Studio开始编译整个解决方案时(假设解决方案中只有一个项目)
编译的意义是什么?
Visual Studio搜索文件.vcxproj并开始编译扩展名为.cpp的每个文件。编译顺序未定义。因此,您不能假设首先编译了main.cpp文件如果.cpp文件依赖于其他.h文件来查找符号可以在.cpp文件中定义,也可以不定义如果存在一个.cpp文件,编译器在其中找不到一个符号,则编译器时间错误将引发消息symbol x not be found对于每个扩展名为.cpp的文件,都会生成一个对象文件.o,并且Visual Studio会将输出写入名为ProjectName.cpp.Clean.txt的文件中,该文件包含链接器必须处理的所有对象文件。
编译的第二步是由Linker完成的。Linker应该合并所有对象文件并最终生成输出(可能是可执行文件或库)
链接项目的步骤
分析所有对象文件,找到仅在头文件中声明的定义(例如:前面的答案中提到的类的一个方法的代码,或者初始化类内部的静态变量的事件)如果在目标文件中找不到一个符号,也会在其他库中搜索。对于将新库添加到项目的配置财产->VC++目录->库目录,您在此处指定了用于搜索库的其他文件夹,以及用于指定库名称的配置财产->链接器->输入。-如果链接器找不到您在一个.cpp中编写的符号,则会引发一个链接器时间错误,听起来可能像错误LNK2001:未解析的外部符号“void __cdecl foo(void)”(?foo@@YAXXZ)
观察
一旦链接器找到一个符号,他就不会在其他库中搜索它链接库的顺序很重要。如果Linker在一个静态库中找到一个外部符号,他会在项目的输出中包含该符号。但是,如果库是共享的(动态的),他不会在输出中包含代码(符号),但可能会发生运行时崩溃
如何解决这种错误
编译器时间错误:
确保编写的c++项目语法正确。
链接器时间错误
定义在头文件中声明的所有符号使用#pragma一次,如果编译的当前.cpp中已包含一个标头,则允许编译器不包含该标头确保外部库中不包含可能与头文件中定义的其他符号冲突的符号使用模板时,请确保在头文件中包含每个模板函数的定义,以允许编译器为任何实例化生成适当的代码。
不支持链接器脚本的GNUld包装器
一些.so文件实际上是GNU ld链接器脚本,例如libtbb.so文件是一个ASCII文本文件,其内容如下:
INPUT (libtbb.so.2)
一些更复杂的构建可能不支持这一点。例如,如果在编译器选项中包含-v,则可以看到mainwin gcc包装器mwdip丢弃要链接的库的详细输出列表中的链接器脚本命令文件。
cp libtbb.so.2 libtbb.so
或者可以用.so的完整路径替换-l参数,例如,代替-ltbb-do/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtb.so.2
跨模块.dll(编译器特定)错误地导入/导出方法/类。
MSVS要求您使用__declspec(dllexport)和__declsspec(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
{
};
已声明但未定义变量或函数。
典型的变量声明是
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
请注意,您实现的函数与您声明的函数完全匹配。例如,您可能有不匹配的简历限定符:
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)
不匹配的其他示例包括
函数/变量在一个命名空间中声明,在另一个命名空间定义。函数/变量声明为类成员,定义为全局(反之亦然)。函数返回类型、参数编号和类型以及调用约定并不完全一致。
来自编译器的错误消息通常会给出已声明但从未定义的变量或函数的完整声明。将其与您提供的定义进行比较。确保每个细节都匹配。