Guaranteed order of global initialization

Pages: 12
@Disch did you see the post JLBorges linked to?
closed account (o1vk4iN6)
Well cout wouldn't need to worry about that as it's being linked from an external library (stdlib).
L B wrote:

@Disch did you see the post JLBorges linked to?


Yes I did, but again he doesn't answer the question of how a global object can be initialized. He only illustrates how a class's static members can be initialized... which I already understand.

I understand that cout's static members can be guaranteed to be initialized with the nifty counter.

What I don't understand is how cout itself (ie: the actual object... the nonstatic members) can be initialized.

Does cout simply not have any nonstatic members?


xerzi wrote:
Well cout wouldn't need to worry about that as it's being linked from an external library (stdlib).


I don't believe this is correct. Are you sure about this or are you just guessing?
Disch wrote:
What I don't understand is how cout itself (ie: the actual object... the nonstatic members) can be initialized.
Look at an implementation, e.g.

GNU libstdc++: http://gcc.gnu.org/viewcvs/gcc/trunk/libstdc%2B%2B-v3/src/c%2B%2B98/ios_init.cc?view=markup
1
2
3
4
5
6
7
8
9
 ios_base::Init::Init()
	  {
	    if (__gnu_cxx::__exchange_and_add_dispatch(&_S_refcount, 1) == 0)
	      {
	        // Standard streams default to synced with "C" operations.
	        _S_synced_with_stdio = true;
	        new (&buf_cout_sync) stdio_sync_filebuf<char>(stdout);
...
                new (&cout) ostream(&buf_cout_sync);


LLVM libc++: http://llvm.org/svn/llvm-project/libcxx/trunk/src/iostream.cpp

1
2
3
4
ios_base::Init::Init()
{
...
    ostream* cout_ptr = ::new(cout) ostream(::new(__cout) __stdoutbuf<char>(stdout, state_types+1));
Last edited on
closed account (N36fSL3A)
I thought everything initializes when the object is first called...
closed account (o1vk4iN6)
So where is cout defined such that it can link to it:

1
2
3
4
5
6
7
8
9
10
namespace std _GLIBCXX_VISIBILITY(default)
{
	_GLIBCXX_BEGIN_NAMESPACE_VERSION
	
	  using namespace __gnu_internal;
	  
	  extern istream cin;
	  extern ostream cout;
	  extern ostream cerr;
	  extern ostream clog;


Wouldn't using the new(&cout) ostream() cause it to be initialized twice assuming it's defined in a source file somewhere ?

e: __attribute__((weak)) :/ damn macros
Last edited on
That's a good question - the example JLBorges linked to uses a reference to the object and it is initialized to refer to just a block of data, but with the cout implementations shown here I am pretty confused.
where is cout defined such that it can link to it:

In GCC? In globals_io.cc, same directory as the ios_init.cc I linked
http://gcc.gnu.org/viewcvs/gcc/trunk/libstdc%2B%2B-v3/src/c%2B%2B98/globals_io.cc?view=markup

Looks like cheating to me. Could you explain? The fake_* is especially concerning me.
http://www.cplusplus.com/forum/general/110827/#msg605118

Tip: Pete Becker's CUJ article is worth reading.
Pete Becker's article says it uses placement new, which is a shitty solution IMO because it requires the constructor to be called twice... which seems extremely flakey to me, especially if the class is polymorphic.

What about doing something like this?

1
2
3
4
5
6
7
8
9
// header

extern uint8_t foo_buffer[ sizeof( Foo ) * 2 ];

static Foo& foo = *reinterpret_cast<Foo*>(foo_buffer + align_x);

static FooInitializer initializer;

// initializer uses placement new on foo_buffer, rather than on 'foo' directly 



This way the ctor/dtor only get called once. The downside is that we're technically dereferencing 'foo_buffer' before its initialized in order to set the 'foo' reference.


Notes:
- add 'align_x' to the offset in the buffer so we can make sure memory is aligned properly
- allocate 2x space for the Foo object (to ensure alignment padding does not overflow)

EDIT: Regarding the downside...

That dereferencing is OK because it's valid allocated memory! The memory itself hasn't been initialized yet... but that doesn't matter because placement new will overwrite it anyway.

I like it!
Last edited on
Pete Becker's article says it uses placement new, which is a shitty solution IMO because it requires the constructor to be called twice... which seems extremely flakey to me, especially if the class is polymorphic.


Apparently you didn't finish the article. You might want to check out the code immediately before the Conclusion header.
Yeah you're right. I just skimmed. Haha... looks like he proposes the same thing I just mentioned. I really need to actually read these things.
> What about doing something like this?
1
2
3
4
> // header
> extern uint8_t foo_buffer[ sizeof( Foo ) * 2 ];
> static Foo& foo = *reinterpret_cast<Foo*>(foo_buffer + align_x);
> static FooInitializer initializer;
> ...
> Notes:
> - add 'align_x' to the offset in the buffer so we can make sure memory is aligned properly
> - allocate 2x space for the Foo object (to ensure alignment padding does not overflow)

1. Defining the type-punned buffer with external linkage (and declaring it as such in the header) can lead to undefined behaviour - it grossly violates the 'strict aliasing' rules of C++.

2. To ensure alignment, use the alignas keyword

1
2
3
4
// header
extern Foo& foo ;

static FooInitializer initializer;


1
2
3
4
5
6
7
// implementation
namespace { alignas(Foo) char foo_buffer[ sizeof(Foo) ] ; } // internal linkage

Foo& foo = *reinterpret_cast<Foo*>(foo_buffer) ; // external linkage
// type-punned; this deliberately breaks strict aliasing rules
// we know it is safe here as 1. foo_buffer is not programmatically visible outside a.cc
// and 2. within the implementation, it is never accessed as an array of char 

Aha! Finally I understand.

Thank you JLBorges.
This thread is awesome, thanks everyone. I'm adding this to my bookmarked topics :)

And woo, 7777th post!
Last edited on
Topic archived. No new replies allowed.
Pages: 12