Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is static object guaranteed to be initialized

Tags:

c++

c++11

I am trying to learn about initialization of static objects. Static initialization seems pretty straight forward, assuming you understand constant expressions and constexpr. Dynamic initialization seems quite a bit more tricky.

[basic.start.init]/4

It is implementation-defined whether the dynamic initialization of a non-local variable with static storage duration is done before the first statement of main. If the initialization is deferred to some point in time after the first statement of main, it shall occur before the first odr-use (3.2) of any function or variable defined in the same translation unit as the variable to be initialized.

footnote 34

A non-local variable with static storage duration having initialization with side-effects must be initialized even if it is not odr-used (3.2, 3.7.1).

[basic.start.init]/5

It is implementation-defined whether the dynamic initialization of a non-local variable with static or thread storage duration is done before the first statement of the initial function of the thread. If the initialization is deferred to some point in time after the first statement of the initial function of the thread, it shall occur before the first odr-use (3.2) of any variable with thread storage duration defined in the same translation unit as the variable to be initialized.

I assume that "the initial function of the thread" refers to main, and not just threads started with 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;

So, if a compiler defers dynamic initialization, it seems that footnote 34 states that s must be initialized at some point. Since there are no other variables with dynamic initialization in the translation unit, there is no other variable to odr-use to force initialization of the variables in tu1. At what point is s guaranteed to have been initialized?

Is main guaranteed to return 1? Also, is there some way to change this program such that it is no longer guaranteed to return 1? Alternatively, if it isn't guaranteed, is there some way to change this program such that it becomes guaranteed?


I broke the code up so that the definition of s is in a different translation unit from main. This avoids the question of whether main is odr used. Given that s is the only object in the translation unit, is it guaranteed that main will return 1?

like image 327
Graznarak Avatar asked Sep 03 '13 19:09

Graznarak


1 Answers

I think that all this wording is there to describe what will happen in dynamic loaded libraries, but without explicitly naming them.

To summarize how I interpret it: a non-local variable with static storage duration and dynamic initialization will:

  1. be initialized before the first odr-use of anything in its translation unit;
  2. possibly, before starting main, but possibly after it.

I interpret the footnote 34 as (but remember that footnotes are not normative):

When anything in a TU is ord-used, then every non-local variable with static storage duration having initialization with side-effects must be initialized, even the variables that are not odr-used.

So, if there is a TU where nothing is ord-used, then its dynamic initializations may not happen.

Example

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);
}

This could print 0 or 1: since anything in TU h2 is never odr-used, it is unspecified when the code initialization of s will be done, if at all.

Naturally, sane compilers will initialize s before main, so it will surely print 1:

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

Now, imagine that h2.cpp is in a shared library:

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

And the main file is now:

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;
}

Compile and run:

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

See? The initialization of s has been delayed! The nice part is that it is impossible to reference anything in the dynamic loaded library without first loading it, and loading it will trigger the dynamic initialization. So all is well.

If you think that using dlopen is cheating, remember that there are compilers that support delay loading shared libraries (VC++ for example), where the system call to load the library will be generated automatically by the compiler just the first time it is needed.

like image 62
rodrigo Avatar answered Oct 12 '22 22:10

rodrigo