5

I am trying to find a good way to ensure the construction and destruction order of static variables. As far as I know about static variables, they are constructed and destructed in the following ways:

  1. Destruction order of static objects are in the reverse order of their construction.

  2. If static variables are defined global space in different files, then their construction order is not guaranteed.

  3. However, if a static variable is defined in a function, then local static variable is constructed when the first time execution hits its declaration.

Based on the rules above, I wrote the following c++ code to ensure static variable b is always destructed before static variable a, which in my experiment ensure the construction order and destruction order:

in file A.h

class A {
 public:
  SomeClass* GetStatic() {
    static SomeClass a;
    return &a;
  }
}

in file B.h:

#include "A.h"
class B {
 public:
  AnotherClass* GetStatic() {
    A::GetStatic();  // a dummy call to force the static local variable in 
                     // A::GetStatic() get initialized before the b.
    static AnotherClass b;
    return &b;
  }
}

In the above example, I put a dummy call A::GetStatic(); right before the declaration of static AnotherClass b;. If rule 3 holds, this ensures a is initialized before b. And because of rule 1, it can be guaranteed that b is destructed before a.

And my questions are:

  1. Can I know whether what I've done is correct or might goes wrong in certain corner case?
  2. Is there a better or best way to ensure the construction or destruction order of static variables?

I also checked the isocpp.org website for the best way to ensure the construction and destruction order of static variables, but the section is still marked as TODO: WRITE THIS UP.

Community
  • 1
  • 1
keelar
  • 5,814
  • 7
  • 40
  • 79
  • I'm reasonably sure the destruction order is not defined for this case - the only guarantee is that objects are destroyed after the end of your code in `main` [carefully choosing the words there, to make sure that the case where "destruction happens before `main` is completely finished"]. – Mats Petersson Dec 29 '15 at 00:46
  • Thanks for the quick reply! Can I know why the destruction order is not defined in this case and why doesn't rule 1 work for this case? A reference would be appreciated. – keelar Dec 29 '15 at 00:48
  • I only have to nitpick, that you IMHO should use references instead of pointers as return value for you `getStatic` functions. Reasons are plenty. (Like better readability, no need to check for `nullptr`, because I say so ;-) ) – Superlokkus Dec 29 '15 at 00:49
  • @Superlokkus: haha makes sense, but since I'm following the google coding style so I only use reference when it's a const. – keelar Dec 29 '15 at 00:51
  • @keelar: Because the compiler will build a list of destructors to call. It doesn't do that dynamically, so the "inside function" ones will be destroyed whenever the compiler/runtime thinks it should be destroyed - which is probably the reverse order they are found in the file, not the order they are constructed as such. – Mats Petersson Dec 29 '15 at 00:53
  • @keelar As admirable I find to read and consider style guides, I especially disapproves the one by Google a lot. When I read them, so many "ok thats odd". I began to think, were just considering them because google is big, not because it really helps. – Superlokkus Dec 29 '15 at 00:54
  • @Mats Petersson: Thanks for the clarification! `the compiler will build a list of destructors to call. It doesn't do that dynamically` --- do you happen to have c++ documentation / reference that support this part? – keelar Dec 29 '15 at 01:03
  • 1
    @MatsPetersson Nope. ["If the completion of the constructor or dynamic initialization of an object with static storage duration is sequenced before that of another, the completion of the destructor of the second is sequenced before the initiation of the destructor of the first."](http://eel.is/c++draft/basic.start.term#1) – T.C. Dec 29 '15 at 01:07
  • This is awesome! Thanks T.C! Would you mind putting them into an answer instead of a comment? I think it's worth than a comment. – keelar Dec 29 '15 at 01:09
  • I haven't tested this, but I doubt that common CRTs would bother doing all the list bookkeeping required to guarantee the reverse order of destruction in case of local statics. The order is probably undefined (I guess) no matter what the standard says. – Sergei Kulik Dec 29 '15 at 01:37
  • @Sergei Kulik: I tested in couple compilers but haven't found one that produces unwanted result, but I want to double check whether it is correct and whether it is the suggested way to handle the dependency. – keelar Dec 29 '15 at 01:43
  • @T.C. does that mean that, in the general case of two unrelated static objects in separate units, they may not be destroyed in reverse order of construction? – M.M Dec 29 '15 at 02:28
  • @M.M If you don't start a thread, the dynamic initialization is indeterminately sequenced (so one is sequenced before another, but unspecified which), and so destruction will be in reverse order. – T.C. Dec 29 '15 at 10:45

2 Answers2

4

In your case you are using construct on first use, and none of the constructors of your classes dependent on the other. Thus the order of initialization is guaranteed A then B.

The destruction order in this case it is guaranteed to be as such B->A, as long as you have simple destructors. Here is a more elaborate answer.

Community
  • 1
  • 1
g24l
  • 3,055
  • 15
  • 28
  • Can I further know whether the destruction order is guaranteed to be B then A in this case because of its construction order is A then B? – keelar Dec 29 '15 at 00:53
  • @keelar Sorry man for not putting that in the answer, that wasn't part of the question in the first read, but maybe I missed that. In your case it is guaranteed, the second form of COFU is not. – g24l Dec 29 '15 at 00:55
  • 1
    Also it's worth reading [this answer][2], especially [the comments][3]. [2]: http://stackoverflow.com/a/9968204/3537677 [3]: http://stackoverflow.com/questions/9968162/destructor-for-static-fields-singleton-realization/9968204#comment12734084_9968204 – Superlokkus Dec 29 '15 at 11:30
0

Sadly, but what you've done is currently a "state of the art".

Another useful techniques to google for:

#pragma init_seg(XXX) - works in MSVS and Intel C++ under Windows

__attribute__ ((init_priority(XXX)) - works in GCC and clang

Also, I have to warn you that the local static trick of yours is not thread save (at least in MSVC, Intel C++, clang and gcc) as it is implemented by checking and setting a global variable.

Sergei Kulik
  • 344
  • 2
  • 9
  • Certainly in gcc and clang of recent times, it IS threadsafe. – Mats Petersson Dec 29 '15 at 00:54
  • If `A::GetStatic()` is called first, then `a` is constructed before `b`. If `B::GetStatic()` is called first, then internally it will first call A::GetStatic() then declare `b.` Can I know where it might introduce the thread safety issue? – keelar Dec 29 '15 at 00:58
  • It is thread-safe in VS 2015. See here: http://stackoverflow.com/questions/28096970/when-is-a-divide-by-zero-not-a-divide-by-zero-a-puzzle-in-the-debugger-static – PaulMcKenzie Dec 29 '15 at 01:12
  • Non standard solutions are always cheap – Superlokkus Dec 29 '15 at 01:12
  • @MatsPetersson gcc++ 4.7: `17 static C T; => 0x00000000004007ef <+11>: mov $0x600ef0,%eax 0x00000000004007f4 <+16>: movzbl (%rax),%eax 0x00000000004007f7 <+19>: test %al,%al 0x00000000004007f9 <+21>: jne 0x400839 ` - Clearly a race condition, so you are mistaken. – Sergei Kulik Dec 29 '15 at 01:12
  • Again, for those who believe that the compiler will do your job to assure thread safety: it won't and this is by design. If you don't believe me - look at disassembly. It is not thread safe in MSVC 2013, Intel C++ 2013, gcc 4.x, clang and I am pretty sure it won't be thread safe in near future. – Sergei Kulik Dec 29 '15 at 01:18
  • @Sergei Kulik: Thanks for further attach the assembly. I am trying to understand where is the race but still didn't get it. Can I know in which situation the completion of the construction of `b` could be finished before the completion of the construction of `a` in my example? – keelar Dec 29 '15 at 01:25
  • @Sergei - The future is here already! If you use the latest versions of each compiler, it **is** working. – Bo Persson Dec 29 '15 at 01:27
  • @keelar, Say after `mov $0x600ef0,%eax` the current thread is preempted before being able to execute `test %al,%al ` and another thread calls the same function. In this case the construction of T will be called twice. I am not saying that the problem will hit you. Might never happen in lifetime even in a true multi threaded program. But paying attention to such possibilities is a way to go with quality software. – Sergei Kulik Dec 29 '15 at 01:34
  • according to [this thread](http://stackoverflow.com/questions/8102125/is-local-static-variable-initialization-thread-safe-in-c11), static variable initialization is thread-safe in C++11. Of course, some compilers may be bugged, although the comments claim that gcc has done thread-safe static initialization since 2001. Perhaps you could start a new question with a MCVE if it appears a compiler is not doing it correctly. – M.M Dec 29 '15 at 02:32
  • g++ 4.7, the oldest version of g++ which supports C++11, [does implement a guard properly](http://goo.gl/F1hW8X). So I'm not sure what your claims of gcc 4.x are about. – M.M Dec 29 '15 at 02:42
  • @M.M Too long to post in a comment: https://drive.google.com/open?id=0B-cPOX3znWXqck5BLWcwYVN1Z2c As you can see the constructor changes global state, so the lock cannot be optimized out. Simply there is no lock in the first place. – Sergei Kulik Dec 29 '15 at 03:49
  • Do you see the line `callq 0x400668 <__cxa_guard_acquire@plt>` in the output? – M.M Dec 29 '15 at 04:23
  • 1
    Also you linked to g++ 4.4.7, which is a pre-C++11 version. The first version accepting `-std=c++11` was 4.7. But according to [this table](http://en.cppreference.com/w/cpp/compiler_support) g++ has implemented it correctly since 4.3 . – M.M Dec 29 '15 at 04:29
  • 2
    @M.M I have to admit that you are right. I was too fast to come to conclusions regarding gcc. Even though the first test is not guarded, it is done for speedup only as there is a proper guard around T's constructor. Will check other compilers. – Sergei Kulik Dec 29 '15 at 04:40
  • Added output of Microsoft's and Intel's compilers to the file above. (For those who still follow the discussion) Both do not produce proper locking. So only gcc is secure so far. – Sergei Kulik Dec 29 '15 at 04:53
  • I'm 100% sure clang also is safe. – Mats Petersson Dec 29 '15 at 08:25
  • @MatsPetersson Yup, have checked on Xcode 5.1, the guard is there. Added output to the file above. – Sergei Kulik Dec 29 '15 at 22:12