链接¶
参考:
链接(linkage
)指的是程序中全局符号(如变量、类型名和函数名)在整个翻译单元中的可见性,它影响程序在链接阶段的处理
为什么要学习链接¶
单定义规则¶
在C++
程序中,符号(global
,比如变量名或函数名)可以在同一作用域(scope
)内多次声明(declaration
),但是只能有一次定义(definition
),称之为单定义规则(one definition rule
, 简称为ODR
)
- 变量定义指的是变量初始化
- 函数定义指的是指定函数返回值(
signature
)和函数体实现
程序组成¶
一个程序由多个翻译单元(translation unit
)组成,每个翻译单元由一个源文件(.cpp/.cxx
等)加上其直接或间接引入的头文件组成。编译器首先单独编译每一个翻译单元,再由链接器编译这些翻译单元到一个程序
链接问题¶
如果违反ODR
,在不同链接单元存在同一个名称的定义,那么会造成链接失败
最好的解决方案是将变量定义放置在一个头文件中,然后在各个源文件中通过#include
方式引入该头文件,并通过include guard
方式保证该头文件仅被编译一次,这样就能解决重复定义问题
另一种方式是区分符号的内部(internal
)链接和外部(external
)链接,以保证符合ODR
链接 vs. 作用域¶
- 链接的概念是指程序中全局符号(如变量、类型名和函数名)在单个翻译单元中的可见性
- 作用域的概念是指声明的符号(如名称空间、类或函数体)在整个程序中的可见性
内部 vs. 外部¶
链接的作用是判定符号是否仅在当前翻译单元内可见
默认具有内部链接的对象如下:
const
对象constexpr
对象typedef
声明类型- 名称空间中的静态对象
默认具有外部链接的对象如下:
- 非
const
全局变量 free function
:指的是定义在全局或名称空间作用域的函数
内部链接对象仅在单个翻译单元中可见,所以其它单元中可能存在相同名称的全局对象(变量、类定义等);外部链接对象的名称在整个程序中唯一
外部转内部¶
声明static
能够改变全局符号的链接特性为内部,示例如下:
// named.h
#ifndef FIRST_NAMED_H
#define FIRST_NAMED_H
#include <iostream>
namespace zj {
void hello();
}
#endif //FIRST_NAMED_H
// named.cpp
#include "named.h"
static int aa = 3;
void zj::hello() {
std::cout << aa << std::endl;
}
// main.cpp
static char aa = 'a';
int main() {
zj::hello();
cout << aa << endl;
}
在main.cpp
和named.cpp
中定义相同的全局符号名aa
,不会造成链接错误。结果如下:
3
a
内部转外部¶
声明const
对象为extern
,同时给定一个值,能够改变链接特性为外部
extern const int value = 42;
extern¶
参考:extern (C++)
extern
关键字有以下四种使用方式:
- 作用于非
const
全局变量,指定该变量的定义来自于其他翻译单元。除了定义该全局变量的单元外,所有其他单元上该变量的声明必须加上extern
- 作用于
const
全局变量,指定该变量拥有外部链接。在其他翻译单元上该变量的声明必须加上extern
extren "C"
表示函数在其他单元上定义并使用C
语言调用规范。可以作用于单个函数,也可使用块操作,表示作用于多个函数- 作用于模板声明,它指定模板已在其他地方实例化。这是一种优化,它告诉编译器它可以重用另一个实例化,而不是在当前位置创建一个新的实例化
示例一¶
作用于非全局对象,除了对象定义,所有对象声明都需要加上extern
// named.cpp
extern char aa;
// main.cpp
char aa = 'a';
示例二¶
作用于const
对象,默认情况下const
对象拥有内部链接,所以需要const
声明和定义中都要加上extern
说明符
//fileA.cpp
extern const int i = 42; // extern const definition
//fileB.cpp
extern const int i; // declaration only. same as i in FileA
示例三¶
使用extern "C"
表示对象在别处定义并符合C
语言调用规范
// Cause everything in the specified
// header files to have C linkage.
extern "C" {
// add your #include statements here
#include <stdio.h>
}
// Declare the two functions ShowChar
// and GetChar with C linkage.
extern "C" {
char ShowChar(char ch);
char GetChar(void);
}
// Declare a global variable, ee, with C linkage.
extern "C" int ee;
不能同时存在extern "C"
和extern "C++"
extern "C" {
int open(const char *pathname, int flags); // C function declaration
}
int main()
{
int fd = open("test.txt", 0); // calls a C function from a C++ program
}
// This C++ function can be called from C code
extern "C" void handler(int) {
std::cout << "Callback invoked\n"; // It can use C++
}