3

考虑以下代码,分为三个编译单元:

a.h

struct A
{
    void Register(const char* s);

    const char* m_s[10];
    int m_i = 0;
};

A& GetA();

a.cpp

#include "a.h"
#include <stdio.h>

void A::Register(const char* s)
{
    m_s[m_i++] = s;
}

A& GetA()
{
    static A instance;
    return instance;
}

int main(int argc, char* argv[])
{
    A& a = GetA();
    int n = a.m_i;
    for (int i = 0; i < n ; ++i)
        printf("%s\n", a.m_s[i]);
    return 0;
}

b.cpp

#include "a.h"
struct B
{
    B() { GetA().Register("b"); }

    static B* instance;
};
B* B::instance = new B;

c.cpp

#include "a.h"
struct C
{
    C() { GetA().Register("c"); }

    static C* instance;
};
C* C::instance = new C;

代码使用 gcc (-std=c++11) 构建并运行良好,产生输出:

c
b

现在,参考cppreference.com

延迟动态初始化

动态初始化是发生在主函数的第一条语句(对于静态)还是线程的初始函数(对于线程局部变量)之前,或者推迟到之后发生,这是实现定义的。

如果非内联变量的初始化延迟发生在主/线程函数的第一条语句之后,它发生在任何变量的第一次 odr 使用之前,该变量在与该变量相同的翻译单元中定义为静态/线程存储持续时间被初始化。如果给定的翻译单元没有使用任何变量或函数,则该翻译单元中定义的非局部变量可能永远不会被初始化(这模拟了按需动态库的行为)。但是,只要 TU 中的任何东西被 odr-used,所有初始化或销毁有副作用的非局部变量都将被初始化,即使它们没有在程序中使用。

请注意,a.cpp不知道 and 的存在,B并且& withC的唯一交互是在它们各自实例的构造过程中的调用和。BCAGetA()A::Register()

据我所知,B&C实例不是 ODR 使用的,当然也不是来自main()的翻译单元。他们的初始化显然有副作用,但在我看来,不能保证这种初始化会在进入之前发生main(),或者在main()打印注册的字符串之前发生 - 或者根本不发生。

所以 - 最后 -我的问题是BandC实例在main()打印注册字符串之前被初始化不是因为标准,而是因为 gcc 的实现定义的行为吗?

如果有标准保证,怎么保证?

4

1 回答 1

0

BC实例在打印注册字符串之前被初始化不是因为main()标准,而是因为 gcc 的实现定义的行为吗?

标准不保证。报价中最相关的部分:

如果一个给定的翻译单元没有使用任何变量或函数,则该翻译单元中定义的非局部变量可能永远不会被初始化

由于 nor 没有使用函数或变量b.cpp,因此c.cpp它们的静态变量可能未初始化(关于动态初始化),因此它们初始化的副作用可能不可见。


在实践中,我希望翻译单元静态链接时显示的初始化行为,以及动态加载(共享库)时可能的非初始化行为。但是标准都没有保证,因为它没有指定共享库的行为方式。

于 2017-07-20T10:48:40.623 回答