链接器如何处理跨翻译单元的相同模板实例化?

2024-04-18

假设我有两个翻译单元:

foo.cpp

void foo() {
  auto v = std::vector<int>();
}

bar.cpp

void bar() {
  auto v = std::vector<int>();
}

当我编译这些翻译单元时,每个翻译单元都会实例化std::vector<int>.

我的问题是:它在链接阶段如何工作?

  • 两个实例化是否具有不同的损坏名称?
  • 链接器是否将它们作为重复项删除?

C++ requires那一个内联函数定义 http://en.cppreference.com/w/cpp/language/inline存在于引用该函数的翻译单元中。模板成员 函数是隐式内联的,但默认情况下也使用外部实例化 连锁。因此,当以下情况时,链接器将看到重复的定义: 相同的模板在不同的环境中使用相同的模板参数进行实例化 翻译单位。链接器如何处理这种重复是你的问题。

您的 C++ 编译器受 C++ 标准约束,但您的链接器不受约束 关于如何链接 C++ 的任何编纂标准:它本身就是一条法则, 植根于计算历史,与对象的源语言无关 对其链接进行编码。您的编译器必须与目标链接器一起工作 可以并且将会这样做,以便您可以成功链接您的程序并查看它们的情况 你所期望的。所以我将向您展示 GCC C++ 编译器如何与 GNU 链接器处理不同翻译单元中相同的模板实例化。

该演示利用了这样一个事实:虽然 C++ 标准requires- 由一种定义规则 http://en.cppreference.com/w/cpp/language/definition- 同一模板的不同翻译单元中的实例化 相同的模板参数应具有相同的定义,编译器 - 当然 - 不能对不同人之间的关系强制执行任何这样的要求 翻译单位。它必须信任我们。

因此,我们将在不同的环境中使用相同的参数实例化相同的模板 翻译单元,但我们会通过将宏观控制的差异注入到 随后将显示不同翻译单元中的实现 我们链接器选择哪个定义。

如果您怀疑此作弊会使演示无效,请记住:编译器 无法知道 ODR 是否是ever荣获不同翻译单位的荣誉, 所以它在这个帐户上的行为不会有所不同,而且不存在这样的事情 作为“欺骗”链接器。无论如何,演示将证明它是有效的。

首先我们有我们的作弊模板标题:

东西.hpp

#ifndef THING_HPP
#define THING_HPP
#ifndef ID
#error ID undefined
#endif

template<typename T>
struct thing
{
    T id() const {
        return T{ID};
    }
};

#endif

宏的价值ID是我们可以注入的跟踪值。

接下来是源文件:

foo.cpp

#define ID 0xf00
#include "thing.hpp"

unsigned foo()
{
    thing<unsigned> t;
    return t.id();
}

它定义了函数foo,其中thing<unsigned>是 实例化来定义t, and t.id()被返回。通过成为一个函数 实例化的外部链接thing<unsigned>, foo服务于目的 的:-

  • 强制编译器进行实例化
  • 公开链接中的实例化,以便我们可以探究 链接器用它来做。

另一个源文件:

boo.cpp

#define ID 0xb00
#include "thing.hpp"

unsigned boo()
{
    thing<unsigned> t;
    return t.id();
}

这就像foo.cpp除了它定义boo代替foo和 套ID = 0xb00.

最后是程序源码:

main.cpp

#include <iostream>

extern unsigned foo();
extern unsigned boo();

int main()
{
    std::cout << std::hex 
    << '\n' << foo()
    << '\n' << boo()
    << std::endl;
    return 0;
}

该程序将打印十六进制的返回值foo()- 我们的作弊应该做什么 =f00- 那么返回值boo()- 我们的作弊应该做=b00.

现在我们将编译foo.cpp,我们将这样做-save-temps因为我们想要 看看大会:

g++ -c -save-temps foo.cpp

这将程序集写入foo.s利息部分是 的定义thing<unsigned int>::id() const(损坏=_ZNK5thingIjE2idEv):

    .section    .text._ZNK5thingIjE2idEv,"axG",@progbits,_ZNK5thingIjE2idEv,comdat
    .align 2
    .weak   _ZNK5thingIjE2idEv
    .type   _ZNK5thingIjE2idEv, @function
_ZNK5thingIjE2idEv:
.LFB2:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movq    %rdi, -8(%rbp)
    movl    $3840, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

顶部的三个指令很重要:

.section    .text._ZNK5thingIjE2idEv,"axG",@progbits,_ZNK5thingIjE2idEv,comdat

这个函数将函数定义放在它自己的链接部分中,称为.text._ZNK5thingIjE2idEv如果需要的话,将输出,合并到.text(即代码)链接目标文件的程序部分。 A 像这样的链接部分,即.text.<function_name>被称为功能部分。 这是一个包含以下内容的代码部分only函数的定义<function_name>.

该指令:

.weak   _ZNK5thingIjE2idEv

至关重要。它分类thing<unsigned int>::id() const as a weak https://en.wikipedia.org/wiki/Weak_symbol象征。 GNU 链接器识别strong符号和weak符号。对于一个强符号来说, 链接器仅接受链接中的一个定义。如果有更多,它将给出倍数 - 定义错误。但对于弱符号来说,它可以容忍任意数量的定义, 并选择一个。如果一个弱定义的符号在链接中也有(只有一个)强定义,那么 将选择强定义。如果一个符号有多个弱定义且没有强定义, 然后链接器可以选择any one任意的弱定义。

该指令:

.type   _ZNK5thingIjE2idEv, @function

分类thing<unsigned int>::id()指的是function- 不是数据。

然后在定义体中,代码在地址处汇编 由弱全局符号标记_ZNK5thingIjE2idEv, 本地同一个 贴上标签.LFB2。该代码返回 3840 (= 0xf00)。

接下来我们来编译boo.cpp一样的方法:

g++ -c -save-temps boo.cpp

再看看如何thing<unsigned int>::id()定义于boo.s

    .section    .text._ZNK5thingIjE2idEv,"axG",@progbits,_ZNK5thingIjE2idEv,comdat
    .align 2
    .weak   _ZNK5thingIjE2idEv
    .type   _ZNK5thingIjE2idEv, @function
_ZNK5thingIjE2idEv:
.LFB2:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movq    %rdi, -8(%rbp)
    movl    $2816, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

它是相同的,除了我们的作弊:这个定义返回 2816 (= 0xb00)。

当我们在这里时,让我们注意一些可能不言而喻的事情: 一旦我们进入汇编(或目标代码),班级已经消失。这里, 我们要做的是:-

  • data
  • code
  • 符号,可以标记数据或标记代码。

所以这里没有具体代表的实例化 thing<T> for T = unsigned。剩下的一切thing<unsigned>在这种情况下是 的定义_ZNK5thingIjE2idEv a.k.a thing<unsigned int>::id() const.

所以现在我们知道什么是compiler关于实例化thing<unsigned>在给定的翻译单元中。如果必须实例化一个thing<unsigned>成员函数,然后组装实例化成员的定义 函数位于标识成员函数的弱全局符号处,并且它 将此定义放入其自己的功能部分中。

现在让我们看看链接器做了什么。

首先我们将编译主源文件。

g++ -c main.cpp

然后链接所有目标文件,请求诊断跟踪_ZNK5thingIjE2idEv, 和一个链接映射文件:

g++ -o prog main.o foo.o boo.o -Wl,--trace-symbol='_ZNK5thingIjE2idEv',-M=prog.map
foo.o: definition of _ZNK5thingIjE2idEv
boo.o: reference to _ZNK5thingIjE2idEv

所以链接器告诉我们程序得到了定义_ZNK5thingIjE2idEv from foo.o and calls it in boo.o.

运行该程序表明它说的是实话:

./prog

f00
f00

Both foo() and boo()正在返回的值thing<unsigned>().id() 实例化于 foo.cpp.

变成了什么样子other的定义thing<unsigned int>::id() const in boo.o?地图文件向我们展示了:

prog.map

...
Discarded input sections
 ...
 ...
 .text._ZNK5thingIjE2idEv
                0x0000000000000000        0xf boo.o
 ...
 ...

链接器丢弃了函数部分boo.o那 包含其他定义。

现在让我们链接prog再次,但这次与foo.o and boo.o在里面 相反的顺序:

$ g++ -o prog main.o boo.o foo.o -Wl,--trace-symbol='_ZNK5thingIjE2idEv',-M=prog.map
boo.o: definition of _ZNK5thingIjE2idEv
foo.o: reference to _ZNK5thingIjE2idEv

这次,程序得到了定义_ZNK5thingIjE2idEv from boo.o和 调用它foo.o。该计划确认:

$ ./prog

b00
b00

地图文件显示:

...
Discarded input sections
 ...
 ...
 .text._ZNK5thingIjE2idEv
                0x0000000000000000        0xf foo.o
 ...
 ...

链接器丢弃了函数部分.text._ZNK5thingIjE2idEv from foo.o.

这样就完成了图片。

编译器在每个翻译单元中发出一个弱定义 每个实例化的模板成员都在其自己的函数部分中。链接器 然后只需选择first它遇到的那些弱定义 在链接序列中,当需要解析对弱的引用时 象征。因为每个弱符号都涉及一个定义,所以任何 其中一个 - 特别是第一个 - 可用于解析所有引用 到链接中的符号,其余的弱定义是 消耗品。多余的弱定义必须被忽略,因为 链接器只能链接给定符号的一个定义。还有剩余的 弱定义可以是丢弃的由链接器提供,无需抵押品 对程序造成损害,因为编译器将每个链接单独放置在链接节中。

通过选择first它看到的弱定义,链接器是有效的 随机选择,因为目标文件的链接顺序是任意的。 不过这样也好,只要我们遵守多个翻译单位的命令, 因为我们这样做了,那么所有的弱定义确实是相同的。通常的做法是#include- 从头文件中的任何地方使用类模板(并且这样做时不宏注入任何本地编辑)是遵守规则的相当可靠的方法。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

链接器如何处理跨翻译单元的相同模板实例化? 的相关文章

随机推荐

  • CSS 滤色器叠加

    我正在尝试在图像上创建颜色叠加层 就像在这个应用程序中一样 图像上的绿色叠加层 https i stack imgur com FdgdA png https i stack imgur com FdgdA png 对我来说 他们看起来并不
  • Word 2007 VBA:ActiveDocument.CustomXMLParts

    In 本教程 https learn microsoft com en us archive blogs erikaehrli data driven document generation with word 2007 and the o
  • 从 ViewPager 中删除由 FragmentStatePagerAdapter 填充的所有片段

    我有一个 ViewPager 我使用 FragmentStatePagerAdapter 填充片段 代表 arrayListOfObjects 中的对象 一切正常 mMyFragmentPagerAdapter new fragmentAd
  • 更少的 css 编译器。无法使用变暗属性

    我正在开发一个项目 使用 LESS 作为我的 CSS 编译器 我已经有一个完全工作的循环 可以正确设置背景颜色 我的问题是这样的 使用我当前的代码 当我尝试使用 darken 属性时 编译结果是这样的 SyntaxError 错误评估函数d
  • 我应该使用哪个功能接口?

    我正在学习编写一些 lambda 表示形式功能接口 https docs oracle com en java javase 11 docs api java base java lang FunctionalInterface html
  • UIView 的 viewDidLoad 吗?

    是什么viewDidLoad for UIView 我有一个UIView与 xib 一起 我想在加载时隐藏它的子视图之一 我尝试使用这个 id initWithCoder NSCoder aDecoder theView hidden YE
  • 使用 Json.NET 反序列化空数组

    我有一个使用 Json NET v7 0 1 的 C 应用程序 作为 REST 调用的结果 我以以下形式返回一些 JSON messages phoneNumber 123 456 7890 smsText abcd1234 phoneNu
  • 如何使用 Jquery 更改辅助样式表?

    我的网站上有我的 base css 和 red css 我喜欢在按下某个按钮时将 red css 更改为 blue css 而不丢失 base css 如何执行此操作 我试过这个
  • Dataframe 根据其他列创建新列

    我有一个数据框 df lt data frame a c 1 2 3 4 5 b c 1 20 3 4 50 df a b 1 1 1 2 2 20 3 3 3 4 4 4 5 5 50 我想根据现有列创建一个新列 像这样的事情 if df
  • for 语句后的大括号

    我是新手 编写一个代码来打印从 1 到 10 的数字之和 事情是这样的 for a 1 a lt 10 a sum a cout lt
  • excel函数“R中决策函数的搜索

    我有一个问题 R中是否可以实现excel函数 决策搜索 R中有没有函数或者需要在R中创建一个脚本 需要结合以下方程来求解X值得到 1 126 作为结果 1 126 X X 0 2 EXP X 0 2 1 您可以使用以下方法找到该方程的解un
  • .vimrc 控制键映射不起作用

    我的 vimrc 中有以下映射 用于映射控制键 1 2 3 用于切换选项卡 我在 ubuntu 11 10 中使用 gnome 终端 控制键映射似乎不起作用 谁能告诉我我做错了什么 VIM Vi 改进版 7 3 154 map
  • 如何在不关闭vim的情况下重置vim的设置(包括插件,.vimrc文件)?

    我已经使用 vim 两年了 我使用 pathogen 进行插件管理 我在寻找 一种在不关闭 vim 的情况下重置所有 vim 设置 包括插件 vimrc 文件 的方法 是 有什么想法吗 PS 在某些情况下 重新加载 vimrc 与重新打开
  • Android 在其他设备上共享显示

    我正在努力与通过 WiFi 连接的多个表 全部有根 共享平板电脑显示屏 我正在使用以下方法 全部在一个线程内 1 我截屏 Process sh Runtime getRuntime exec su null null OutputStrea
  • 涵盖 .NET 中的 TDD、DDD 和设计模式的书籍

    我想要一本能够真正让我全面了解使用 C TDD ASP NET MVC DDD 和设计模式 例如存储库模式 的现代 ASP NET 开发的书 我非常擅长 C 和 ASP NET MVC 但想填补空白 如果您对涵盖这些主题的一两本书有很好的体
  • 如何更改 graphviz 的默认字体大小?

    我使用 doxygen graphviz 来记录我的代码 graphviz 在生成图像方面做得很好 有什么方法可以更改 graphviz 的默认字体大小吗 默认值为 14 但我想使用 12 更改单个元素 例如节点 子图 边缘等 的字体大小确
  • 具有重复参数(可变参数)的磁铁模式

    是否可以使用磁铁图案 http spray io blog 2012 12 13 the magnet pattern 与可变参数 object Values implicit def fromInt x Int Values implic
  • getattr/setattr/hasattr/delattr 线程安全吗?

    看这个单例实现 if not hasattr Singleton instance with Singleton instance lock if not hasattr Singleton instance Singleton insta
  • Maven 父项目自动化

    我有这个结构 child C 家长 A child B 如果我尝试在没有父级的情况下安装子级 B maven 会抛出一个错误 我知道按照惯例我应该将父级安装在我的存储库中 但是有没有办法让某人拉取子级并在安装过程中安装自动父辈 编辑 我要尽
  • 链接器如何处理跨翻译单元的相同模板实例化?

    假设我有两个翻译单元 foo cpp void foo auto v std vector