C ++ 11在链接上调用注册函数?

时间:2012-12-10 00:16:27

标签: c++ c++11

有没有办法只通过链接其.o文件来调用函数?

例如:

Foo.cpp中:

extern int x;

void f() { x = 42; }

struct T { T() { f(); } } t; // we use constructor of global
                             // object to call f during initialization

bar.cpp:

#include <iostream>

int x;

int main()
{
    std::cout << x;
}

编译/链接/运行:

$ g++ -c foo.cpp
$ g++ -c bar.cpp
$ g++ foo.o bar.o
$ ./a.out
42

这似乎适用于gcc 4.7。它按预期输出42。但是我记得在一些旧的编译器上我遇到了这个模式的问题,因为没有什么真正“使用”foo.o它在链接时被优化了。 (也许这个特定的例子由于某种原因不代表问题)

C ++ 11标准对这种模式有什么看法?它能保证有效吗?

3 个答案:

答案 0 :(得分:8)

我相信你没有摆脱困境。该标准并不保证您的代码按预期工作,尽管许多人依赖该行为进行各种“自我注册”构造。

您的对象t已动态初​​始化,这会产生调用f的副作用。关于静态存储对象的动态初始化(3.6.2 / 4,“非局部变量的初始化”),标准就是这样说的:

  

实现定义是否使用静态存储动态初始化非局部变量   持续时间在主要的第一个陈述之前完成。如果初始化延迟到某个时间点   在第一个main语句之后,它应该在任何函数或变量的第一次使用(3.2)之前发生   在与要初始化的变量相同的翻译单元中定义。

在您的代码中,只有x使用了odr,但x在主翻译单元中定义。其他TU的变量或函数在您的程序中没有使用,因此从技术上讲,无法保证t将被初始化。从技术上讲,每个TU的某些必须由程序的静态控制流引用,以便将所有内容初始化。

正如我所说,有很多真实世界的代码带有“自注册”翻译单元(例如在字符串键控映射中注册工厂函数指针),因此只需将TU添加到最终程序最终会有更多功能。我被告知大多数编译器都会无条件地初始化所有全局变量,因为不这样做会破坏很多现实世界的代码。但不要依赖它!

答案 1 :(得分:3)

标准并没有真正说明如何选择翻译单元组合成一个完整的程序,据我所知,在C ++ 98和C ++ 11之间没有什么重要的改变。

在实践中,当您将TU链接为.o时,无论如何都会获得静态初始值设定项,而如果将其作为.a的一部分链接,则只会如果TU中的其他内容从main()或另一个链接为.o的文件中被传递,则获取其静态初始值设定项。 ld的{​​{1}}标记会覆盖此标记并提取存档的每个成员,就像您将其列为个人--whole-archive一样。其他连接器可能会以不同的方式处理它。

答案 2 :(得分:2)

相关部分是2.2 [lex.phases]第1段,第8和第9项:

  

0.8。翻译的翻译单元和实例化单元组合如下:[注意:这些中的一些或全部可以从库中提供。 -end note]检查每个翻译的翻译单元以产生所需实例化的列表。 [注意:这可能包括已明确请求的实例化(14.7.2)。 -end note]找到所需模板的定义。实现定义是否需要包含这些定义的翻译单元的来源。 [注意:实现可以将足够的信息编码到翻译的翻译单元中,以确保此处不需要源。 -end note]执行所有必需的实例化以生成实例化单元。 [注意:这些与翻译的翻译单元类似,但不包含对未实例化模板的引用,也不包含模板定义。 -end note]如果任何实例化失败,程序就会格式不正确。

     

0.9。解析所有外部实体引用。链接库组件以满足对当前转换中未定义的实体的外部引用。所有这些转换器输出都被收集到程序映像中,该映像包含在其执行环境中执行所需的信息。

项目8是我能找到的最好的,表明需要包括所有翻译单元。第9项只是说明了解决符号所需的一切也被引入。实际上,这意味着明确地包括翻译单元具有所需的效果。但是,将翻译单元放入库中则不然。我想这就是你过去所经历的:将实现放入库中,并希望它们在启动时注册。由于相应的转换单元中没有符号解析未引用的符号,因此库中的目标文件不会被拉入,相应地全局对象也不会被初始化。