9

有没有办法通过链接它的 .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 标准对这种模式有什么看法?能保证工作吗?

4

3 回答 3

8

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

您的对象t是动态初始化的,这具有调用f. 该标准对静态存储对象的动态初始化有这样的说法(3.6.2/4,“非局部变量的初始化”):

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

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

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

于 2012-12-10T00:34:14.890 回答
3

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

In practice, when you link a TU as a .o, you'll get its static initializers no matter what, while if you link it as a piece of a .a, you'll only get its static initializers if something else in the TU was referenced transitively from main() or another file linked as a .o. ld's --whole-archive flag overrides this and pulls in each member of the archive as if you listed it as an individual .o. Other linkers may handle this differently.

于 2012-12-10T00:36:03.967 回答
2

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

.8. 翻译后的翻译单元和实例化单元组合如下: [ 注意:其中一些或全部可能来自库。—尾注] 检查每个翻译的翻译单元以生成所需实例化的列表。[注意:这可能包括已明确请求的实例化(14.7.2)。—尾注] 所需模板的定义位于。是否需要包含这些定义的翻译单元的源是由实现定义的。[注意:实现可以将足够的信息编码到翻译的翻译单元中,以确保此处不需要源。—尾注] 执行所有必需的实例化以生成实例化单元。[注意:这些类似于翻译的翻译单元,但不包含对未实例化模板的引用和模板定义。— 尾注] 如果任何实例化失败,则程序是非良构的。

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

第 8 项是我能找到的最好的,表明所有翻译单元都必须包含在内。第 9 项只是指出解析符号所需的所有内容也都被引入。实际上,这意味着显式包含翻译单元具有预期的效果。但是,将翻译单元放入库中则不然。我想这就是您过去所经历的:例如,将实现放入库中并希望它们在启动期间注册。由于相应翻译单元中没有符号解析未引用的符号,因此库中的目标文件不会被拉入,相应地全局对象也不会被初始化。

于 2012-12-10T00:31:08.287 回答