什么是未定义的参考/未解析的外部符号错误?常见原因是什么?如何解决/预防?
当前回答
我的例子:
头文件
class GameCharacter : public GamePart
{
private:
static vector<GameCharacter*> characterList;
...
}
.cpp文件:
vector<GameCharacter*> characterList;
这产生了“未定义”加载程序错误,因为“characterList”被声明为静态成员变量,但被定义为全局变量。
我加上这个是因为——虽然其他人在一长串需要注意的事情中列出了这个案例——但这个列表并没有给出示例。这是一个更值得寻找的例子,尤其是在C++中。
修复方法是向全局变量添加限定以定义静态数据成员:
vector<GameCharacter*> GameCharacter::characterList;
同时保持收割台相同。
其他回答
编译器/IDE中的错误
我最近遇到了这个问题,结果发现这是Visual Studio Express 2013中的一个错误。我不得不从项目中删除一个源文件,然后重新添加它以克服错误。
如果您认为这可能是编译器/IDE中的错误,请尝试以下步骤:
清理项目(一些IDE可以选择这样做,您也可以手动删除对象文件)尝试启动新项目,从原始代码复制所有源代码。
在链接共享库时,请确保未隐藏使用的符号。
gcc的默认行为是所有符号都可见。但是,当使用选项-fvisibility=hidden构建转换单元时,只有标记为__attribute__((可见性(“默认”))的函数/符号在生成的共享对象中是外部的。
您可以通过调用以下命令来检查要查找的符号是否为外部符号:
# -D shows (global) dynamic symbols that can be used from the outside of XXX.so
nm -D XXX.so | grep MY_SYMBOL
隐藏/本地符号用小写符号类型的nm表示,例如t而不是代码段的“t”:
nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL
您还可以使用nm和选项-C来定义名称(如果使用了C++)。
与Windows DLL类似,可以使用define标记公共函数,例如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
正在添加模板。。。
给定带有友元运算符(或函数)的模板类型的代码片段;
template <typename T>
class Foo {
friend std::ostream& operator<< (std::ostream& os, const Foo<T>& a);
};
运算符<<被声明为非模板函数。对于与Foo一起使用的每种类型T,都需要有一个非模板运算符<<。例如,如果声明了一个类型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的相应实例化,即运算符<<<int>实例化被限制为访问Foo<int>的实例化的私有成员。
备选方案包括:;
允许友谊扩展到模板的所有实例化,如下所示;模板<typename T>Foo类{模板<typename T1>朋友std::ostream&operator<<(std::estream&os,const Foo<T1>/a);// ...};或者,运算符<<的实现可以在类定义内内联完成;模板<typename T>Foo类{朋友std::ostream&operator<<(std::estream&os,const Foo&a){ /*...*/ }// ...};
注意,当运算符(或函数)的声明仅出现在类中时,该名称不适用于“普通”查找,仅适用于cppreference中的依赖于参数的查找;
首先在类或类模板X中的友元声明中声明的名称将成为X最内部封闭命名空间的成员,但除非在命名空间范围内提供了匹配声明,否则无法进行查找(考虑X的依赖于参数的查找除外)。。。
cppreference和C++常见问题解答中有关于模板朋友的进一步阅读。
显示上述技术的代码列表。
作为失败代码示例的旁注;g++警告如下
警告:friend声明“std::ostream&operator<<(…)”声明非模板函数[-Wnon-template friend]注意:(如果这不是您想要的,请确保函数模板已经声明,并在此处的函数名称后面添加<>)
函数或类方法在源文件中使用内联说明符定义。
例如:-
主.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;
};
口香糖.h(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;
}
gum.cpp(1)
#include "gum.h"
#include <iostream>
inline /* <- wrong! */ void gum()
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
如果指定gum(类似地,foo::bar)在其定义中是内联的,那么编译器将通过以下方式内联gum(如果它选择):-
没有任何独特的口香糖定义,因此不发出任何符号,链接器可以通过该符号引用口香糖的定义,而是将所有对gum的调用替换为编译后的gum主体的内联副本。
因此,如果在源文件gum.cpp中内联定义gum,则编译为对象文件gum.o,其中所有对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的每个源文件中看到它的定义,则只能将gum定义为内联。这意味着它的内联定义需要存在于包含在每个源文件中的头文件中您可以在其中调用gum。做两件事之一:
要么不内联定义
从源文件定义中删除内联说明符:
foo.cpp(2)
#include "foo.h"
#include <iostream>
void foo::bar() const {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
gum.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
口香糖.h(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
对的未定义引用WinMain@16或类似的“不寻常”main()入口点引用(特别是对于visual studio)。
您可能错过了使用实际IDE选择正确的项目类型。IDE可能希望将例如Windows应用程序项目绑定到这样的入口点函数(如上面缺失的引用中所指定的),而不是通常使用的int main(int argc,char**argv);签名
如果IDE支持普通控制台项目,您可能希望选择此项目类型,而不是windows应用程序项目。
下面是从真实世界问题中更详细地处理的案例1和案例2。