4

I have encountered a strange situation recently.

Let's consider the following class (place in header.h):

#ifndef HEADER_H
#define HEADER_H

#include <set>

template <class V, class T>
class Class
{
public:
    typedef std::set<const Class<V, T>* > instances_list;

    explicit Class(const V& Value):m_value(Value)
    {
    s_instances.insert(this);
    }
private:
    static instances_list s_instances;
    V m_value;
};

template <typename V, typename T>
typename Class<V,T>::instances_list Class<V,T>::s_instances;

class Something : public Class<int, Something>
{
public:
    static const Something SOMETHING_CONSTANT;

private:
    explicit Something(int value): Class<int, Something>(value)
    {}
};

#endif

and a very simple application using it:

#include "header.h"

const Something Something::SOMETHING_CONSTANT (1);

int main()
{
}

Compiling it results in various degrees of successfulness.

g++ (4.9.2, 4.8.4 and 4.3.2) compiles an executable, but they produce a SEGFAULT, with a stack trace like:

#0  0x00007ffff7b4aaaa in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#1  0x00000000004012bb in std::_Rb_tree_iterator<Class<int, Something> const*>::operator-- (this=0x7fffffffdcf0) at /usr/include/c++/4.8/bits/stl_tree.h:204
#2  0x0000000000400ef2 in std::_Rb_tree<Class<int, Something> const*, Class<int, Something> const*, std::_Identity<Class<int, Something> const*>, std::less<Class<int, Something> const*>, std::allocator<Class<int, Something> const*> >::_M_get_insert_unique_pos (this=0x6030c0 <Class<int, Something>::s_instances>, __k=@0x7fffffffde08: 0x6030a4 <Something::SOMETHING_CONSTANT>) at /usr/include/c++/4.8/bits/stl_tree.h:1333
#3  0x0000000000400c1d in std::_Rb_tree<Class<int, Something> const*, Class<int, Something> const*, std::_Identity<Class<int, Something> const*>, std::less<Class<int, Something> const*>, std::allocator<Class<int, Something> const*> >::_M_insert_unique (this=0x6030c0 <Class<int, Something>::s_instances>, __v=@0x7fffffffde08: 0x6030a4 <Something::SOMETHING_CONSTANT>) at /usr/include/c++/4.8/bits/stl_tree.h:1377
#4  0x0000000000400b19 in std::set<Class<int, Something> const*, std::less<Class<int, Something> const*>, std::allocator<Class<int, Something> const*> >::insert (this=0x6030c0 <Class<int, Something>::s_instances>, 
    __x=@0x7fffffffde08: 0x6030a4 <Something::SOMETHING_CONSTANT>) at /usr/include/c++/4.8/bits/stl_set.h:463
#5  0x0000000000400ad9 in Class<int, Something>::Class (this=0x6030a4 <Something::SOMETHING_CONSTANT>, Value=@0x7fffffffde24: 1) at header.h:14
#6  0x0000000000400aa2 in Something::Something (this=0x6030a4 <Something::SOMETHING_CONSTANT>, value=1) at header.h:30
#7  0x0000000000400a24 in __static_initialization_and_destruction_0 (__initialize_p=1, __priority=65535) at main.cpp:3
#8  0x0000000000400a6b in _GLOBAL__sub_I__ZN9Something18SOMETHING_CONSTANTE () at main.cpp:7
#9  0x00000000004015ed in __libc_csu_init ()
#10 0x00007ffff751ce55 in __libc_start_main (main=0x4009ed <main()>, argc=1, argv=0x7fffffffdf88, init=0x4015a0 <__libc_csu_init>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdf78) at libc-start.c:246
#11 0x0000000000400929 in _start ()

clang (3.4.1 and 3.5.0-10) produce an executable which runs nicely, does not segfault.

Visual Studio 2015 produces a segfaulting application.

If I put everything in one file the compiler found at ideone.com (http://ideone.com/Dhh8Hl) produces a runtime error, signal 11.

I have the feeling, this is undefined behaviour ... Please correct me if I'm not right.

After reading relevant questions: C++ Static member initalization (template fun inside) , Template static members initialization order and Initialization order of static data inside class template I am still unable to find relevant paragraphs from the standard which tell me why does this fail when compiled with g++ and MSVC but passes on clang.

The (3.6.2) tells me:

Objects with static storage duration (3.7.1) shall be zero-initialized (8.5) before any other initialization takes place. A reference with static storage duration and an object of POD type with static storage duration can be initialized with a constant expression (5.19); this is called constant initialization. Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place. Dynamic initialization of an object is either ordered or unordered. Definitions of explicitly specialized class template static data members have ordered initialization. Other class template static data members (i.e., implicitly or explicitly instantiated specializations) have unordered initialization. Other objects defined in namespace scope have ordered initialization. Objects defined within a single translation unit and with ordered initialization shall be initialized in the order of their definitions in the translation unit. The order of initialization is unspecified for objects with unordered initialization and for objects defined in different translation units.

and from it I understand that Static initialization shall be performed before any dynamic initialization takes place. and in my opinion const Something Something::SOMETHING_CONSTANT (1); falls in the category of constant initialization (please correct me if I'm wrong) thus it is a static initialization. Also, the one above says that Other class template static data members (i.e., implicitly or explicitly instantiated specializations) have unordered initialization. which is fine, since I have only one of those, but I just cannot see why the static template member is not initialized before the actual member of that type.

I have solved the problem using https://isocpp.org/wiki/faq/ctors#static-init-order so now I am just curious why there is so different behaviour from the compilers, and which is correct.

Community
  • 1
  • 1
Ferenc Deak
  • 34,348
  • 17
  • 99
  • 167

1 Answers1

5

The initialization

const Somthing SomeThing::SOMETHING_CONST(1);

is not constant initialization: it initializes a const but does so dynamically, i.e., it is dynamic initialization. Constant initialization happens when computing a constant expression. That's a more specific meaning than just const and only applies to entities which can be computed during compilation (see section 5.19 [expr.const] for more details).

If you want this initialization to happen as constant initialization you need to make your constructor constexpr. Given that you access a std::set<int> during this initialization I doubt that you'll manage to make your constructor constexpr.

This is just the usual peril of using global objects. If you need some level of control of the initialization order use the usual hack to get the global objects at least initialized in a suitable order and wrap them into a function returning a reference to a local static variable. Alternatively you may be able to create something akin to a constexpr version of a std::set<int> which then could be used for constant initialization.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • Nice catch for the non-const but dynamic initialization! Unfortunately I don't have access to c++11, so I'll have to live with this variant. – Ferenc Deak Nov 25 '15 at 11:07
  • Dietmar, is it correct to generalize that anytime a non-local static object(be it namespace-scope, file-scope or extern) is accessed statically, I have to guard agains SIOF? Think, for example, about a function `f()` defined in `1.cpp` being called to initialize a static object `var` in `2.cpp`, and suppose `f()` returns a **non-extern** const `k` (i.e. `k` is defined in `1.cpp` same as `f()`). – Kemal Jan 20 '17 at 10:38
  • Addition to my post above: let `const int k = 99` so that we are in the clear in the sense that `k` itself is not prone to SIOF. – Kemal Jan 20 '17 at 10:50