什么是不明确的参考/解析的外部符号错误? 什么是常见的原因以及如何修复/阻止他们?
随意编辑/添加自己的。
什么是不明确的参考/解析的外部符号错误? 什么是常见的原因以及如何修复/阻止他们?
随意编辑/添加自己的。
编译的C ++程序需要在几个步骤中的地方,如通过指定2.2 (学分基思汤普森为参考) :
翻译的语法规则中的优先级是由以下阶段指定[见脚注。
- 物理源文件中的字符被映射,在一个实现定义的方式,基本源字符集(引入终了行指标新行字符),如果必要的。 [SNIP]
- 反斜线字符(\)紧跟一个新行字符的每个实例被删除时,拼接物理源极线,以形成逻辑源极线。 [SNIP]
- 源文件被分解成预处理标记(2.5)和空白字符(包括注释)序列。 [SNIP]
- 预处理指令被执行,宏调用展开,_Pragma一元运算符表达式的执行。 [SNIP]
- 每个源字符集构件在字符文字或文字字符串,以及在一个字符的每个转义序列和通用字符名称文字或非原始字符串文字,被转换为执行字符集的相应构件; [SNIP]
- 相邻的字符串文字令牌是连接在一起。
- 空白字符分隔标记不再显著。 每个预处理标记转换成令牌。 (2.7)。 将得到的令牌语法和语义分析,并翻译成翻译单元。 [SNIP]
- 翻译的翻译单元和实例化单元组合如下:[SNIP]
- 所有的外部实体引用解决。 库组件链接到满足于目前的翻译没有定义实体的外部引用。 所有此类翻译输出被收集到含有所需的在其执行环境的执行信息的节目的图像。 (重点煤矿)
[脚注]实现必须表现得好像出现这些分开的阶段,虽然在实践中不同的相位可能被折叠在一起。
在编译过程的最后阶段会出现指定的错误,最常见的称为链接。 这基本上意味着你整合了一系列的执行文件到目标文件或库,现在你想,让他们一起工作。
假设你定义的符号a
在a.cpp
。 现在, b.cpp
宣布符号,并用它。 连接之前,它只是假设该符号的某处定义,但它并没有在意在哪里。 链接阶段是负责查找符号和正确地连接起来, b.cpp
(实际上要使用它的物体或库)。
如果你使用Microsoft Visual Studio,你会看到,项目产生.lib
文件。 这些包含输出符号的表,导入符号的表。 导入的符号是解决对你对链接库,并提供了使用该库导出的符号.lib
(如果有的话)。
类似的机制,为其他编译器/平台的存在。
常见的错误信息error LNK2001
, error LNK1120
, error LNK2019
为Microsoft Visual Studio和undefined reference to
符号名称为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
和类似的错误与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
常见原因包括:
#pragma
(微软的Visual Studio) UNICODE
定义 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
可以提供用于一个一个初始化static
const
类定义中整型或枚举类型的数据成员; 然而,ODR使用该构件的仍然需要如上所述的命名空间范围定义。 C ++ 11允许初始化的类的所有内部static const
数据成员。
通常,每个转换单元将产生一个包含在该转换单元中定义的符号的定义的对象文件。 要使用这些符号,你必须对这些目标文件链接。
在GCC你指定是在命令行中被连接在一起的所有目标文件,或编译的实现文件一起。
g++ -o test objectFile1.o objectFile2.o -lLibraryName
该libraryName
这里只是图书馆的裸名,不特定于平台的补充。 因此,例如在Linux库文件通常叫做libfoo.so
但你只写-lfoo
。 在Windows上是相同的文件可能被称为foo.lib
,但你会用同样的理由。 您可能需要增加在那里这些文件可以使用发现的目录-L‹directory›
。 确保以后不会写空间-l
或-L
。
对于的XCode:添加用户头搜索路径- >添加库搜索路径- >拖放实际库引用到项目文件夹。
在MSVS,添加到项目文件自动拥有自己的目标文件链接在一起和lib
文件将产生(在常见的用法)。 要使用一个单独的项目符号,你需要包括lib
在项目设置文件。 这是在项目属性中的连接部分进行,在Input -> Additional Dependencies
。 (该路径lib
文件应在加入Linker -> General -> Additional Library Directories
)当使用配备有一个第三方库lib
文件,如果不这样做通常会导致错误。
它也可以发生,你忘了将文件添加到编辑,在这种情况下将不会生成目标文件。 在GCC你将文件添加到命令行。 在MSVS添加文件到该项目将使其自动编译它(尽管文件可手动,可单独从构建除外)。
在Windows编程,你没有链接必要的库搬弄是非的迹象是,解决的符号的名称开头__imp_
。 中查找函数的名称的文件中,应该说你需要使用哪个库。 例如,MSDN把信息在一个盒子里,在每一个功能的底部在一个名为“库”部分。
一种典型的变量声明是
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)
不匹配的其他例子包括
从编译器的错误信息会经常给你宣布,但从来没有定义的变量或函数的完整声明。 仔细比较给你提供的定义。 确保每一个细节相匹配。
其中库链接顺序事有,如果库互相依赖。 在一般情况下,如果库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
所以,再次重复,顺序确实重要!
什么是“未定义参考/解析的外部符号”
我会尽力解释什么是“未定义参考/解析的外部符号”。
注:我用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
好了,我们破解它:)
所以,作为一个结果 - “未定义参考/解析外部符号错误”发生在链接器无法找到目标文件的全局符号。
如果一切都失败了,重新编译。
最近,我能得到在Visual Studio 2012只是通过重新编译问题的文件摆脱无法解析的外部错误。 当我重新修建,错误就走开了。
当两个(或更多)库有一个循环依赖这通常发生。 库A试图在B.LIB用符号和库B试图从A.LIB使用符号。 既不存在,开始了。 当您尝试编译,链接步骤将会失败,因为它无法找到B.LIB。 A.LIB将产生,但没有DLL。 然后,您编译B,这会成功,产生B.LIB。 重新编译将现在的工作,因为B.LIB现在发现。
函数(或可变的) void foo()
是在一个C程序定义并试图在C使用它++程序:
void foo();
int main()
{
foo();
}
C ++的连接器预计的名字是错位的,所以你必须声明函数为:
extern "C" void foo();
int main()
{
foo();
}
等同地,而不是在C程序,函数(或变量)被定义void foo()
在C ++,但与C链接被定义:
extern "C" void foo();
并尝试在C ++程序用C ++联动使用它。
如果整个库包含在一个头文件(和被编译为C代码); 该包括将需要如下;
extern "C" {
#include "cheader.h"
}
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
{
};
这是每一个VC ++程序员已经出现过一次次最令人困惑的错误消息之一。 让我们把事情明晰第一。
A.什么是象征? 总之,一个符号的名称。 它可以是一个变量名,函数名,类名,typedef名称,或除了那些属于C ++语言的名称和标志什么。 它是用户通过一个依赖库中定义的或引入的(另一个用户定义的)。
B.什么是外部? 在VC,每个源文件(的.cpp,.C等)被认为是一个翻译单元,编译器在一个时间编译一个单元,并生成一个目标文件(.OBJ),用于当前转换单元。 (请注意,这包括源文件每头文件将进行预处理,将被视为该转换单元中的一部分)翻译单元内的一切都被认为是内部的,一切被认为是外部的。 在C ++中,可以通过使用例如关键字引用外部符号extern
, __declspec (dllimport)
等。
C.什么是“解决”? 决心是连接时间期限。 在链接时,链接器尝试找到目标文件中的每一个符号不能在内部找到其定义的外部定义。 这个搜索过程包括的范围:
这个搜索过程称为决心。
D.最后,为什么解析的外部符号? 如果连接器无法找到该内部没有定义符号的外部定义,会报告解析的外部符号错误。
LNK2019的E.可能的原因 :解析的外部符号错误。 我们已经知道,这个错误是由于连接器无法找到外部符号的定义,可能的原因可以排序为:
举例来说,如果我们在a.cpp定义了一个名为foo的功能:
int foo()
{
return 0;
}
在b.cpp我们要调用函数foo,所以我们增加
void foo();
声明函数foo(),并调用它在另一个函数体,说bar()
void bar()
{
foo();
}
现在,当你建立这个代码,你会得到一个错误LNK2019抱怨说,foo是一个未解决的符号。 在这种情况下,我们知道FOO()有其a.cpp定义,但是从我们所呼叫的一个(不同的返回值)不同。 这是定义存在的情况下。
如果我们要调用一些功能在库中,但导入库未添加额外的依赖列表(从设置Project | Properties | Configuration Properties | Linker | Input | Additional Dependency
项目的设置)。 现在,因为该定义不当前搜索范围中不存在连接器将报告一个LNK2019。
非专业化的模板必须有自己的定义,使用它们的所有翻译单元可见。 这意味着你可以不是一个模板的定义分离,以实现文件。 如果必须分开实施,通常的解决办法是有一个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
以头文件或某些地方使用它的翻译单元可见。
专业模板,可以在实现文件来实施和实现不必须是可见的,但专业化必须预先声明。
对于进一步的解释和其他可能的解决方案(显式实例)看到这个问题和答案 。
未定义参照WinMain@16
或类似的“异常” main()
入口点的参考 (特别是对于视觉工作室 )。
你可能已经错过了选择与您的实际IDE合适的项目类型。 IDE可能要绑定例如Windows应用程序的项目,以这样的入口点的功能(如在上面的丢失的引用指定的),而不是通常使用的int main(int argc, char** argv);
签名。
如果您的IDE支持平原控制台项目 ,你可能想选择的,而不是一个Windows应用程序项目该项目类型。
以下是案例1和案例2从真实世界的问题进行更详细的处理。
此外,如果你是使用第三方库请确保您有正确的32/64位二进制
微软提供#pragma
引用链接时正确的库;
#pragma comment(lib, "libname.lib")
除了库路径包括图书馆的目录,这应该是图书馆的全名。
Visual Studio的NuGet包需要新的工具集的版本进行更新
我只是有这个问题,试图链接与Visual Studio 2013年的问题了libpng是包文件只有库Visual Studio 2010和2012。
正确的解决方案是希望开发者版本的更新包,然后升级,但它通过黑客在VS2013额外的设置,在VS2012库文件指向为我工作。
我编辑的包(在packages
通过找到解决方案的目录内的文件夹) packagename\build\native\packagename.targets
和文件中,复制所有的v110
部分。 我改变了v110
到v120
的条件字段只能非常小心留下的文件路径的全部为v110
。 这只是允许的Visual Studio 2013链接到库2012,在这种情况下,它的工作。
假设你有C ++编写的,其具有.cpp文件千和.h files.And一千个大项目,让我们说,这个项目还取决于十项静态库。 让我们说我们是在Windows和我们建立我们在Visual Studio 20XX项目。 当你按下Ctrl + F7 Visual Studio来开始编译整个解决方案(假设我们在解决方案只有一个项目)
什么是编译的意思?
编译的第二步骤是通过Linker.Linker完成应该合并所有对象文件并生成最后的输出(其可以是可执行文件或库)
步骤在链接项目
error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
意见
如何解决这一类型的错误
编译时错误:
链接时间错误
#pragma once
允许编译器不包括一个头,如果它已经包括在编译当前的.cpp 最近,我有这个问题,它原来是在Visual Studio Express的2013的错误 。 我不得不从项目中删除源文件并重新添加它来克服错误。
步骤来尝试,如果你认为它可能是编译器/ IDE中的错误:
大多数现代的接头包括打印出不同程度的一个详细选项;
GCC和铛; 您通常会添加-v -Wl,--verbose
或-v -Wl,-v
到命令行。 更多详情可在这找到;
对于MSVC, /VERBOSE
(特别/VERBOSE:LIB
)被添加到链接命令行。
/VERBOSE
链接器选项 。 链接的.lib文件关联到一个.dll
我遇到过同样的问题。 说我有项目MyProject的和TestProject。 我实际上已经挂了MyProject中的TestProject的lib文件。 不过,制作这种LIB文件作为MyProject的DLL的建成。 另外,我不包含在MyProject的所有方法的源代码,但只获得了DLL的入口点。
为了解决这个问题,我建立了MyProject的作为LIB和链接TestProject此的.lib文件(我复制粘贴生成的.lib文件到TestProject文件夹)。 那么我可以再次MyProject的建设作为一个DLL。 这是因为编译的lib到TestProject链接不包含代码为MyProject的类中的所有方法。
由于人们似乎被定向到这个问题,当谈到链接错误,我要在这里添加此。
一个可能的原因与GCC 5.2.0链接错误是,一个新的libstdc ++库ABI现在是默认选择。
如果你得到关于未定义的引用链接错误,对涉及在STD类型的符号:: __ cxx11命名空间或标签[ABI:cxx11]那么它很可能表明你正在尝试链接在一起的对象是被编译的不同值_GLIBCXX_USE_CXX11_ABI文件宏。 此链接到这是与旧版本的GCC编译的第三方库时经常会发生冲突。 如果第三方库无法与新的ABI重建则需要用旧ABI重新编译代码。
所以,如果你5.1.0后切换到GCC时,突然得到链接错误,这将是检查的事情。
各地GNU LD的包装,不支持链接脚本
一些.so文件实际上是GNU ld的链接脚本 ,例如libtbb.so文件与此内容的ASCII文本文件:
INPUT (libtbb.so.2)
一些更复杂的版本可能不支持这一点。 例如,如果包含-v在编译器选项,你可以看到, 孟文GCC包装mwdip丢弃在库的详细输出列表链接脚本命令要链接的文件中,一个简单的解决办法是更换链接脚本输入指令用替代文件的副本(或符号连接),例如文件
cp libtbb.so.2 libtbb.so
或者你也可以是.so的完整路径,例如,而不是取代-l参数-ltbb
做/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2
给定的模板类型与朋友操作者(或功能)的代码段;
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 ++常见问题解答 。
代码清单上面显示的技术 。
作为一个方面说明的失败代码样品; 克++警告有关此如下
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)
libfoo
依赖libbar
,那么你的正确联动提出libfoo
前libbar
。 undefined reference to
一些错误。 #include
d,并在您链接库中定义的事实。 例子在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
。
如果这是真的,那么连接器将提取的副本my_lib.o
从库,并将其添加到您的程序。 然后,你的程序中包含了一个定义hw
,所以它的引用hw
得到解决 。
当您尝试程序链接,如:
$ gcc -o eg1 -L. -lmy_lib eg1.o
链接器还没有添加 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
如果这是真的,那么链接器不会复制任何数据块进行的libz
,并将它们添加到您的程序; 相反,它只会看病贵程序的代码,以便: -
在运行时,系统程序加载器的副本加载libz
到相同的过程,你的程序时,它加载程序的拷贝,运行它。
在运行时,只要你的程序是指在规定的东西libz
,即参考使用由副本中导出的定义libz
在同一进程。
你的程序要参考的只有一件事具有由导出的定义libz
,即功能zlibVersion
,这被称为只有一次,在eg2.c
。 如果连接器补充说,参考你的程序,然后通过查找导出的定义libz
中,引用解析
但是,当你尝试将程序链接,如:
gcc -o eg2 -lz eg2.o
事件的顺序是错误的一样的方式与例如1.当链接器找到了点-lz
,有在程序没有任何引用:他们都在eg2.o
,其中尚未见过。 因此,连接器决定它没有使用libz
。 当它到达eg2.o
,将其添加到程序,然后有未定义的参考zlibVersion
,联动序列完成; 该参考文献未解决,和联动失败。
最后, pkg-config
实例2的变化具有明显的现在解释。 后壳扩展:
gcc -o eg2 $(pkg-config --libs zlib) eg2.o
变为:
gcc -o eg2 -lz eg2.o
这仅仅是实施例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
所以联动失败,就像如果你运行这两个命令它。 您在失败通知,唯一不同的是,海湾合作委员会已生成编译+链接的情况下临时对象文件,因为你没有告诉它使用eg1.o
。 我们看:
/tmp/ccQk1tvs.o: In function `main'
代替:
eg1.o: In function `main':
其中指定相互依赖链接库的顺序是错的
以错误的顺序把相互依赖库只是一种方式是你可以得到需要的东西后进来的联动比提供的定义文件定义文件。 这是指他们的目标文件之前,把库是犯同样的错误的另一种方式。
链接错误时会发生一个头文件及其关联的共享库(.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
是不同类型的两个项目中,只要编译器而言。 这可能发生,因为他们来自两个略有不同的包含文件(也许包括文件来自两个不同版本的库)。
DUMPBIN是你的朋友,如果你正在使用Visual Studio。 我相信其他的编译器有其他类似的工具。
过程是这样的:
[1]通过项目我的意思是一组源文件被链接在一起,以产生任一库或一个可执行文件。
编辑1:重写了首节以更容易理解。 请在下面发表评论,让我知道如果别的东西需要修理。 谢谢!
UNICODE
定义 一个Windows UNICODE构建与内置TCHAR
等定义为wchar_t
。如果没有使用建筑等UNICODE
定义为建立与TCHAR
定义为char
等,这些UNICODE
和_UNICODE
定义影响到所有的“ T
”字符串类型 ; LPTSTR
, LPCTSTR
和他们的麋鹿。
建立一个库UNICODE
定义,并试图将其在项目链接UNICODE
没有被定义会导致连接错误,因为会有的定义不匹配TCHAR
; char
与wchar_t
。
错误通常包括官能团与一个值char
或wchar_t
派生类型,这些可能包括std::basic_string<>
等为好。 当通过在代码中受影响的功能浏览时,存在往往会与参考TCHAR
或std::basic_string<TCHAR>
等,这是一个告诉般的迹象,表明代码最初是针对两者UNICODE和多字节字符(或“窄”)的构建。
为了解决这个问题,构建所有需要的库和项目一致的定义UNICODE
(和_UNICODE
)。
这可与完成;
#define UNICODE #define _UNICODE
或者在项目设置;
项目属性>常规>项目默认设置>字符集
或者在命令行上;
/DUNICODE /D_UNICODE
另一种方法是适用的,以及,如果UNICODE不旨在被使用,确保定义未设置,和/或所述多字符设置是在项目中使用和一致地应用。
不要忘了是“发布”和“调试”之间是一致的建立为好。
A中构建的“清洁”可以去除“死木”,可能留下了先前Build躺在身边,构建失败,不完整的建立等构建系统相关的建立问题。
通常,IDE或构建将包括某种形式的“干净”的功能的,但是这可能不被正确地配置(例如在手动生成文件)或可能会失败(例如在中间或所得二进制只读)。
一旦“干净的”已完成,验证“干净”已经成功且所有所生成的中间文件(例如,一个自动生成文件)已被成功地删除。
这个过程可以看作是最后的手段,但往往是一个良好的开端 ; 特别是如果涉及到该错误的代码最近已加入(本地或从源存储库)。
const
变量声明/定义(仅限于C ++) 对于人们在C它可能是一个惊喜,在C ++全球未来const
变量有内部(或静态)联动。 在C,这是不是这样的,因为所有的全局变量是隐式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;
另外一个可以声明const
与明确file1.cpp变量extern
尽管这是一个多认同的答案一个很老的问题,我想与大家分享如何解决一个不起眼的 “未定义的引用”的错误。
我使用别名来引用std::filesystem::path
:文件系统是因为C ++ 17标准库,但我需要的程序也编译C ++ 14所以我决定用一个变量别名:
#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:
注意的main.cpp和file.h.使用不同库 由于main.cpp中#include'd后< 文件系统 >“file.h”,文件系统的版本中使用有C ++的17之一 。 我用下面的命令来编译程序:
$ g++ -g -std=c++17 -c main.cpp
- >编译的main.cpp到main.o
$ g++ -g -std=c++17 -c file.cpp
- >编译file.cpp和file.h到file.o
$ g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs
- >链接main.o和file.o
这种方式包含在file.o 任何功能 ,在main.o中使用的需要path_t
因为main.o提到了“未定义的引用”错误std::filesystem::path
,但file.o到std::experimental::filesystem::path
。
为了解决这个问题我只需要在file.h改变<实验::文件系统>到<文件系统>。
GCC的默认行为是所有符号都是可见的。 然而,当翻译单元与内置选项-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
隐藏/本地的符号由示出nm
与小写符号类型,例如t
代替`T为码截面:
nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL
还可以使用nm
使用选项-C
到还原函数的名称(如果使用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
图书馆在拿起文件夹。