13

我正在尝试了解静态对象的初始化。假设您了解常量表达式和constexpr. 动态初始化似乎有点棘手。

[basic.start.init]/4

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

脚注 34

一个具有静态存储持续时间且具有副作用的初始化的非局部变量必须被初始化,即使它不是 odr-used (3.2, 3.7.1)。

[basic.start.init]/5

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

我假设“线程的初始函数”是指主线程,而不仅仅是以 std::thread 开头的线程。

h1.h

#ifndef H1_H_
#define H1_H_

extern int count;

#endif

tu1.cpp

#include "h1.h"

struct S
{
   S()
   {
      ++count;
   }
};

S s;

tu2.cpp

#include "h1.h"

int main(int argc, char *argv[])
{
   return count;
}

tu3.cpp

#include "h1.h"

int count;

因此,如果编译器推迟动态初始化,脚注 34 似乎表明s必须在某个时候进行初始化。由于翻译单元中没有其他具有动态初始化的变量,因此没有其他变量可以使用 odr 来强制初始化 tu1 中的变量。什么时候可以s保证已经初始化?

main 是否保证返回 1?另外,有没有办法改变这个程序,使它不再保证返回 1?或者,如果不能保证,有没有办法改变这个程序,使它变得有保证?


我分解了代码,以便 的定义smain. 这避免了是否main使用 odr 的问题。鉴于这s是翻译单元中唯一的对象,是否保证main会返回 1?

4

3 回答 3

5

我认为所有这些措辞都是为了描述动态加载的库中会发生什么,但没有明确命名它们。

总结一下我的解释:具有静态存储持续时间和动态初始化的非局部变量将:

  1. 在其翻译单元中的任何内容第一次 odr 使用之前被初始化;
  2. 可能,在开始之前main,但可能在它之后。

我将脚注 34 解释为(但请记住,脚注不是规范性的):

当一个 TU 中的任何东西被 ord-used 时,每个具有静态存储持续时间且具有副作用的初始化的非局部变量都必须被初始化,即使是未使用 odr-used 的变量。

因此,如果有一个 TU 没有使用任何东西,那么它的动态初始化可能不会发生。

例子

h1.h

extern int count;
struct S
{
    S();
};

h1.cpp

#include "h1.h"

int count;
S::S()
{
   ++count;
}

h2.cpp

#include "h1.h"
S s;

主文件

#include "h1.h"
#include <stdio.h>
int main()
{
    printf("%d\n", count);
}

这可能会打印 0 或 1:因为 TU h2 中的任何内容都不会被 ODR 使用,因此未指定代码初始化的s时间(如果有的话)。

自然,理智的编译器会s在 main 之前初始化,所以它肯定会打印1

$ g++ main.cpp h2.cpp h1.cpp -o test1
$ ./test1
1

现在,想象一下它h2.cpp在一个共享库中:

$ g++ -shared -fPIC h2.cpp -o h2.so

主文件现在是:

main2.cpp

#include "h1.h"
#include <dlfcn.h>
#include <stdio.h>

int main()
{
    printf("%d\n", count);
    dlopen("./h2.so", RTLD_NOW);
    printf("%d\n", count);
    return 0;
}

编译并运行:

$ g++ -shared -fPIC h2.cpp -o h2.so
$ g++ -rdynamic main.cpp h1.cpp -ldl -o test2
$ ./test2
0
1

看?的初始化s已延迟!好的部分是,如果不先加载动态加载库,就无法引用动态加载库中的任何内容,加载它会触发动态初始化。所以一切都很好。

如果您认为 usingdlopen是作弊,请记住有些编译器支持延迟加载共享库(例如 VC++),其中加载库的系统调用将在第一次需要时由编译器自动生成。

于 2013-09-03T21:08:32.737 回答
3

无需在定义中搜索正确的页面,我可以说您的程序保证返回 1。每个静态或全局初始化都是在 main 中的第一个命令之前完成的。首先初始化全局变量,然后执行全局对象的构造函数。函数/方法范围内的静态变量在首次使用前初始化。但是有一个陷阱:

int count;

struct A
{
   A()
   {
     count=5;
   }
};

struct B
{
   B()
   {
     count=count*2;
   }
};


A a;
B b;

void main(void)
{
  return count;
}

正如 Ben Voigt 的评论中提到的,如果两个实例都是在同一个翻译单元中创建的,则结果是定义的。因此,在我的示例中,结果为 10。如果实例是在不同的文件中创建的(并分别编译为不同的 .obj 文件),则未定义结果。

于 2013-09-03T19:54:36.017 回答
0

“在与要初始化的变量相同的翻译单元中定义的任何函数或变量的第一次 odr 使用”包括要初始化的变量。

于 2013-09-03T19:54:47.237 回答