代码之家  ›  专栏  ›  技术社区  ›  Leedehai

链接器如何处理具有不同链接的变量?

  •  2
  • Leedehai  · 技术社区  · 6 年前

    在C和C++中,我们可以操纵变量的链接。有三种链接:无链接、内部链接和外部链接。我的问题可能与为什么这些被称为“链接”(这与链接器有何关系)有关。

    我知道链接器能够处理具有外部链接的变量,因为对该变量的引用不局限于单个转换单元中,因此不局限于单个对象文件中。在操作系统课程中,我们通常会讨论如何在幕后实现这一点。

    但是链接器如何处理没有链接的变量(1)和具有内部链接的变量(2)?这两种情况有什么不同?

    3 回复  |  直到 6 年前
        1
  •  1
  •   Jerry Coffin    6 年前

    链接器通常不涉及内部链接或不涉及链接——它们完全由编译器在链接器进入实际操作之前解决。

    内部链接意味着同一翻译单元中不同作用域的两个声明可以引用同一事物。

    无连接

    没有链接意味着同一翻译单元中不同作用域的两个声明不能引用同一个对象。

    所以,如果我有这样的东西:

    int f() { 
        static int x; // no linkage
    }
    

    …没有其他声明 x 在任何其他范围内都可以指 . 链接器只涉及到它通常必须在可执行文件中生成一个字段,告诉它可执行文件所需的静态空间大小,这将包括此变量的空间。因为它永远不能被任何其他声明引用,所以链接器没有必要在这之外介入(特别是,链接器与解析名称无关)。

    内部连接

    内部链接意味着同一翻译单元中不同作用域的声明可以引用同一对象。例如:

    static int x;  // a namespace scope, so `x` has internal linkage
    
    int f() { 
        extern int x; // declaration in one scope
    }
    
    int g() { 
        extern int x; // declaration in another scope
    }
    

    假设我们把它们放在一个文件中(也就是说,它们最终作为一个翻译单元),两个文件中的声明 f() g() 指同一件事 定义为 static 在命名空间范围内。

    例如,考虑如下代码:

    #include <iostream>
    
    static int x; // a namespace scope, so `x` has internal linkage
    
    int f()
    {
        extern int x;
        ++x;
    }
    
    int g()
    {
        extern int x;
        std::cout << x << '\n';
    }
    
    int main() {
        g();
        f();
        g();
    }
    

    这将打印:

    0
    1
    

    …因为 递增的 f() 是一样的 正在打印 g() .

    链接器在这里的参与可以(通常是)与在无链接情况下几乎相同——变量 需要一些空间,链接器在创建可执行文件时指定该空间。是的 但是,需要参与确定 f() g() 双方声明 ,他们指的是相同的 --编译器可以确定。

    我们可以在生成的代码中看到这一点。例如,如果我们用gcc编译上面的代码,则 f() g() 是这些。

    f:

        movl    _ZL1x(%rip), %eax
        addl    $1, %eax
        movl    %eax, _ZL1x(%rip)
    

    这是 (它使用这个名字 _ZL1x 为了它)。

    克:

        movl    _ZL1x(%rip), %eax
        [...]
        call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_c@PLT
    

    所以这基本上就是 ,然后发送到 std::cout (我遗漏了其他我们不关心的参数的代码)。

    重要的是代码引用了 _ZL1x型 --与同名 f 使用,所以它们都引用同一个对象。

    链接器并没有真正参与,因为它看到的只是这个文件为一个静态分配的变量请求了空间。它为它腾出了空间,但不必做任何事 f型 g 引用同样的东西——编译器已经处理过了。

        2
  •  3
  •   torek    6 年前

    就C++本身而言,这并不重要:唯一重要的是系统作为一个整体的行为。不应链接没有链接的变量;不应跨翻译单元链接具有内部链接的变量;应跨翻译单元链接具有外部链接的变量。(当然,作为编写C++代码的人,您必须遵守所有的代码。 你的 约束也一样。)

    然而,在编译器和链接器程序套件中,我们当然 必须关心这个。达到预期结果的方法由我们决定。一种传统的方法非常简单:

    • 没有链接的标识符甚至从未传递到链接器。

    • 具有内部链接的标识符也不会传递给链接器,或者 传递给链接器,但标记为“仅用于此翻译单元”。也就是说,没有 .global 为他们申报,或者有 .local 他们的声明,或类似的。

    • 具有外部链接的标识符被传递到链接器,并且如果链接器看到内部链接标识符,则这些外部链接符号被不同地标记,例如,具有 .全球 是否申报 .本地 宣言。

    如果您有一个Linux或类Unix系统,请运行 nm 在对象上( .o )编译器生成的文件。注意,有些符号用大写字母注释,如 T D 对于文本和数据:它们是全局的。其他符号用小写字母注释,如 t d :这些是本地的。因此,这些系统使用“传递内部链接到链接器,但标记它们的方式与外部链接不同”的方法。

        3
  •  1
  •   John Bollinger    6 年前

    我的问题可能与为什么这些被称为“链接”(这与链接器有何关系)有关。

    根据C标准,

    在不同作用域或同一作用域中声明的标识符 不止一次可以通过 称为链接的进程。

    术语“linkage”似乎相当合适——同一标识符的不同声明是 链接的 使它们一起引用同一个对象或函数。作为所选择的术语,一个真正实现链接的程序通常被称为“链接器”,这是很自然的。

    但是链接器如何处理没有链接的变量(1)和具有内部链接的变量(2)?这两种情况有什么不同?

    链接器不必对没有链接的标识符执行任何操作。对象标识符的每一个这样的声明都声明一个不同的对象(函数声明总是有内部或外部链接)。

    链接器也不一定对具有内部链接的标识符执行任何操作,因为编译器通常可以对这些标识符执行所有需要执行的操作。然而,具有内部链接的标识符可以在同一翻译单元中多次声明,这些标识符都指同一对象或函数。最常见的情况是 static 带有转发声明的函数:

    static void internal(void);
    
    // ...
    
    static void internal(void) {
        // do something
    }
    

    文件范围变量也可以具有内部链接和多个声明,这些声明都链接到引用同一对象,但多个声明部分对变量没有那么有用。