什么是未定义的参考/未解析的外部符号错误?常见原因是什么?如何解决/预防?
当前回答
函数或类方法在源文件中使用内联说明符定义。
例如:-
主.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
其他回答
不同的架构
您可能会看到这样的消息:
library machine type 'x64' conflicts with target machine type 'X86'
在这种情况下,这意味着可用符号用于不同于您正在编译的体系结构。
在Visual Studio上,这是由于错误的“平台”,您需要选择正确的平台或安装正确版本的库。
在Linux上,这可能是由于错误的库文件夹(例如,使用lib而不是lib64)。
在MacOS上,可以选择在同一文件中传送两种体系结构。可能是链接希望两个版本都存在,但只有一个版本存在。也可能是库所在的lib/lib64文件夹错误。
班级成员:
纯虚拟析构函数需要实现。
声明析构函数pure仍然需要定义它(与常规函数不同):
struct X
{
virtual ~X() = 0;
};
struct Y : X
{
~Y() {}
};
int main()
{
Y y;
}
//X::~X(){} //uncomment this line for successful definition
这是因为在隐式销毁对象时调用基类析构函数,因此需要定义。
虚拟方法必须实现或定义为纯方法。
这类似于没有定义的非虚拟方法,增加了如下推理:纯声明会生成一个虚拟vtable,您可能会在不使用函数的情况下得到链接器错误:
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() {}
如果实现在类定义之外,但在头中,则必须将方法标记为内联,以防止多重定义。
如果使用,则需要定义所有使用的成员方法。
一个常见的错误是忘记限定名称:
struct A
{
void foo();
};
void foo() {}
int main()
{
A a;
a.foo();
}
定义应为
void A::foo() {}
静态数据成员必须在类外部的单个转换单元中定义:
struct X
{
static int x;
};
int main()
{
int x = X::x;
}
//int X::x; //uncomment this line to define X::x
可以为类定义中的整型或枚举类型的静态常量数据成员提供初始值设定项;然而,odr使用这个成员仍然需要如上所述的命名空间范围定义。C++11允许在类内初始化所有静态常量数据成员。
正在添加模板。。。
给定带有友元运算符(或函数)的模板类型的代码片段;
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]注意:(如果这不是您想要的,请确保函数模板已经声明,并在此处的函数名称后面添加<>)
我正在构建一个共享/动态库。它在Linux和*BSD上运行,但在Mac OS X上,完全相同的编译和链接命令会产生未解决的引用错误。有什么好处?
Mac OS X在内部与Linux和*BSD非常不同。对象/可执行文件格式为
在Linux和*BSD上,当构建共享库时,默认情况下允许未解析的引用。期望它们在加载时能够满足主可执行文件和/或其他共享库的要求。如果在加载时无法解析这些符号,则共享库将无法加载。
在Mac OS X上,构建动态库时,默认情况下不允许未解析的引用。如果希望在加载时解析引用,则需要显式启用未解析的引用。这是使用未定义的dynamic_lookup链接器标志完成的。
在构建可加载插件时,允许未解析的引用非常有用。
Visual Studio NuGet包需要更新以获得新的工具集版本
我在尝试将libpng与Visual Studio 2013链接时遇到了这个问题。问题是,包文件只有Visual Studio 2010和2012的库。
正确的解决方案是希望开发人员发布更新的软件包,然后进行升级,但这对我来说是有效的,因为我在VS2013的一个额外设置中进行了黑客攻击,指向了VS2012库文件。
我通过找到packagename\build\native\packagename.targets并在该文件中编辑了包(在解决方案目录中的packages文件夹中),复制了所有v110部分。我在条件字段中将v110更改为v120,只是非常小心地将文件名路径全部保留为v110。这只是允许Visual Studio 2013链接到2012年的库,在本例中,它起了作用。