链接

参考:

Program and Linkage (C++)

Language linkage

链接(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.cppnamed.cpp中定义相同的全局符号名aa,不会造成链接错误。结果如下:

3
a

内部转外部

声明const对象为extern,同时给定一个值,能够改变链接特性为外部

extern const int value = 42;

extern

参考:extern (C++)

extern关键字有以下四种使用方式:

  1. 作用于非const全局变量,指定该变量的定义来自于其他翻译单元。除了定义该全局变量的单元外,所有其他单元上该变量的声明必须加上extern
  2. 作用于const全局变量,指定该变量拥有外部链接。在其他翻译单元上该变量的声明必须加上extern
  3. extren "C"表示函数在其他单元上定义并使用C语言调用规范。可以作用于单个函数,也可使用块操作,表示作用于多个函数
  4. 作用于模板声明,它指定模板已在其他地方实例化。这是一种优化,它告诉编译器它可以重用另一个实例化,而不是在当前位置创建一个新的实例化

示例一

作用于非全局对象,除了对象定义,所有对象声明都需要加上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++
}