C++ 编程
链接器是一个生成可执行文件的程序。链接器解决链接问题,例如在某个翻译单元中定义并在其他翻译单元中需要的符号或标识符的使用。在单个翻译单元之外需要使用的符号或标识符具有外部链接。简而言之,链接器的任务是通过找出哪个其他对象定义了有问题的符号来解析对未定义符号的引用,并将占位符替换为该符号的地址。当然,这个过程比这更复杂;但基本思想适用。
链接器可以从称为库的集合中获取对象。根据库(系统或语言或外部库)和传递的选项,它们可能只包含从其他目标文件或库中引用的符号。存在用于各种目的的库,并且通常默认情况下会链接一个或多个系统库。我们将在本书的库部分中更深入地了解库。
将编译器生成的代码文件与创建可执行程序(或库)所需的库连接或组合的过程称为链接。链接是指将程序从多个翻译单元构建起来的方式。
C++ 程序可以与用其他语言编写的程序进行编译和链接,例如 C、Fortran、汇编语言和 Pascal。
- 适当的编译器分别编译每个模块。C++ 编译器将每个“.cpp”文件编译成一个“.o”文件,汇编器将每个“.asm”文件汇编成一个“.o”文件,Pascal 编译器将每个“.pas”文件编译成一个“.o”文件,等等。
- 链接器在单独的步骤中将所有“.o”文件链接在一起,创建最终的可执行文件。
每个函数要么具有外部链接,要么具有内部链接。
具有内部链接的函数仅在一个翻译单元内可见。当编译器编译具有内部链接的函数时,编译器会将该函数的机器代码写入某个地址,并将该地址放入对该函数的所有调用中(这些调用都在该翻译单元中),但会从“.o”文件中删除对该函数的所有提及。如果对某个明显具有内部链接但似乎未在此翻译单元中定义的函数进行了一些调用,则编译器可以立即告知程序员有关该问题(错误)。如果存在一些具有内部链接但从未被调用的函数,则编译器可以进行“死代码消除”,并将其从“.o”文件中排除。
链接器永远不会听说过那些具有内部链接的函数,因此它对它们一无所知。
用外部链接声明的函数在多个翻译单元内可见。当编译器在某个翻译单元中编译对该函数的调用时,它不知道该函数在哪里,因此它会在对该函数的所有调用中留下一个占位符,以及“.o”文件中的指令,用具有该名称的函数的地址替换该占位符。如果该函数从未定义,则编译器不可能知道这一点,因此程序员直到很久以后才会收到有关该问题(错误)的警告。
当编译器编译(定义)具有外部链接的函数(在其他翻译单元中)时,编译器会将该函数的机器代码写入某个地址,并将该地址和该函数的名称放入“.o”文件中,供链接器查找。编译器假设该函数将从其他翻译单元(其他“.o”文件)调用,并且必须将该函数保留在此“.o”文件中,即使最终发现该函数从未从任何翻译单元调用。
大多数代码约定规定头文件只包含声明,不包含定义。大多数代码约定规定实现文件(“.cpp”文件)只包含定义和局部声明,不包含外部声明。
这会导致“extern”关键字只用在头文件中,从未用在实现文件中。这会导致内部链接只在实现文件中指示,从未在头文件中指示。这会导致“static”关键字只用在实现文件中,从未用在头文件中,除非“static”用在头文件中的类定义内部,它表示的含义与内部链接不同。
我们将在本书的文件组织部分中更详细地讨论头文件和实现文件。
static 关键字可以用四种不同的方式使用
- 内部链接
当在自由函数、全局变量或全局常量上使用时,它指定内部链接(与extern
不同,后者指定外部链接)。内部链接将对数据或函数的访问限制在当前文件中。
不在任何函数或类内部使用示例
static int apples = 15;
- 定义一个名为apples的“静态全局”变量,初始值为 15,仅从此翻译单元可见。
static int bananas;
- 定义一个名为bananas,初始值为 0,仅从此翻译单元可见。
int g_fruit;
- 定义一个名为g_fruit的全局变量,初始值为 0,从每个翻译单元可见。此类变量通常不被认为是良好的风格。
static const int muffins_per_pan=12;
- 定义一个名为muffins_per_pan的变量,仅在此翻译单元可见。static 关键字在此处是多余的。
const int hours_per_day=24;
- 定义一个名为hours_per_day的变量,仅在此翻译单元可见。(这与).
static const int hours_per_day=24;
static void f();
- 声明存在一个函数f不带参数且没有返回值,在该翻译单元中定义。此类前向声明通常在定义相互递归函数时使用。
static void f(){;}
- 定义函数f()如上所述声明。此函数只能从该翻译单元中的其他函数和成员调用;它对其他翻译单元不可见。
C++ 标准库中的所有实体都具有外部链接。
extern
关键字告诉编译器某个变量是在另一个源模块(在当前作用域之外)中定义的。然后链接器找到此实际声明,并将extern
变量设置为指向正确的位置。由extern
语句描述的变量将不会为其分配任何空间,因为它们应该在其他地方正确定义。如果某个变量被声明为 extern,而链接器找不到它的实际声明,它将抛出“未解析的外部符号”错误。
示例
extern int i;
- 声明存在一个名为i 的 int 类型变量,在程序中的某个地方定义。
extern int j = 0;
- 定义一个变量j具有外部链接;
extern
关键字在此处是多余的。
extern void f();
- 声明存在一个函数f不接受任何参数,并且在程序中的某个地方没有定义返回值;
extern
是多余的,但有时被认为是好的风格。
extern void f() {;}
- 定义函数f()在上面声明;同样,
extern
关键字在这里在技术上是多余的,因为外部链接是默认的。
extern const int k = 1;
- 定义一个常量int k值为1并且具有外部链接;extern是必需的,因为const变量默认情况下具有内部链接。
extern
语句经常用于允许数据跨越多个文件的作用域。
当应用于函数声明时,额外的“C”或“C++”字符串文字将在使用相反语言编译时更改名称修饰。也就是说,extern "C" int plain_c_func(int param);
允许 C++ 代码执行 C 库函数 plain_c_func。