是否保证静态对象被初始化

时间:2013-09-03 19:30:17

标签: c++ c++11

我正在尝试了解静态对象的初始化。假设您理解常量表达式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)之前发生,其中线程存储持续时间与变量在同一个转换单元中定义待初始化。

我假设“线程的初始函数”是指main,而不仅仅是以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。由于在转换单元中没有其他具有动态初始化的变量,因此没有其他变量可用于强制初始化tu1中的变量。 s保证在什么时候被初始化?

主要保证返回1吗?还有,有什么方法可以改变这个程序,使其不再保证返回1?或者,如果不能保证,有没有办法改变这个程序,以保证它?


我打破了代码,因此s的定义与main的翻译单位不同。这避免了main是否使用的问题。鉴于s是翻译单元中唯一的对象,是否保证main将返回1?

3 个答案:

答案 0 :(得分:5)

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

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

  1. 在其翻译单位中的任何内容的第一次使用之前进行初始化;
  2. 可能,在开始main之前,但可能在之后。
  3. 我将脚注34解释为(但请记住,脚注不是规范性的):

      

    当TU中的任何东西被ord-used使用时,必须初始化每个具有静态存储持续时间且具有side-e效果初始化的非局部变量,即使是非常用的变量。

    因此,如果有一个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;
    

    的main.cpp

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

    这可以打印0或1:因为TU h2中的任何内容都不会被使用,所以当s的代码初始化完成时,它是未指定的,如果有的话。

    当然,理智的编译器会在main之前初始化s,因此肯定会打印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的初始化已被延迟!好的部分是,如果没有先加载它就不可能在动态加载库中引用任何内容,加载它将触发动态初始化。一切都很好。

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

答案 1 :(得分: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文件),则结果未定义。

答案 2 :(得分:0)

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