Daniel Stephens
Daniel Stephens

Reputation: 3239

_Orphan_range crash when using static vector

In my project, I use the constructor of a static object to collect pointers, like a registration method. Very simply, no magic. But during the start I experience a crash, and I can't explain what's happening here. The crash is reproducible on Windows with MSVC or Clang, both using the MSVC headers. Given is the following simple example. Can anyone give me a hint why this could cause issues?

This code seems to work just fine in GCC and Clang on Linux:

https://gcc.godbolt.org/z/vSKdpW

bar.cpp

static int bar = 1;

static Registration abc(&bar);

foo.cpp

static std::vector<void*> registrations;

void add_to_array(void* p)
{
    registrations.push_back(p);
}

foo.h

class Registration
{
public:
    Registration(void* op)
    {
        add_to_array(op);
    }
};

Executing results in the following crash (_Pnext was 0x8.)

void _Orphan_range(pointer _First, pointer _Last) const { // orphan iterators within specified (inclusive) range
#if _ITERATOR_DEBUG_LEVEL == 2
    _Lockit _Lock(_LOCK_DEBUG);

    _Iterator_base12** _Pnext = &_Mypair._Myval2._Myproxy->_Myfirstiter;
    while (*_Pnext) {    <=======================   **_Pnext** was 0x8.

Does anyone know why a static vector can't be used to simply collect pointers to objects? foo.cpp is the only file that uses the vector with push_back. The array is not modified anywhere else.

Upvotes: 9

Views: 2206

Answers (1)

Camille
Camille

Reputation: 485

Having debugged a similar issue just now using VS2019 runtime, I'll go ahead and say this is almost assuredly caused by a zero initialized (but not constructed) vector.

The forensics, in my case, looked like this: the value of _Pnext was 0x8 because _Myproxy is null. With _ITERATOR_DEBUG_LEVEL set to 2 (i.e.: in Debug profile), _Myproxy gets dereferenced, leading to this crash, but Release won't crash since that code is skipped altogether.

Now, looking at MSVC142/VS2019's implementation of vector, you'll see that every single constructor calls _Alloc_proxy, which is what allocates some memory for _Myproxy and assigns the value. So if the constructor had been invoked, you would've necessarily have a non-null _Myproxy.

Static initialization in C++ occurs in two steps: zero initialization, followed by static initialization in an arbitrary, linker determined order. It appears that in the order you end up with, Foo.cpp's Registration gets constructed before Bar.cpp's registrations vector, effectively calling push_back on a zero initialized (but not constructed) vector.

So as the comments suggested, you should definitely rearrange your static initialization so that your vector is properly constructed before using it.

(In my case, I was allocating memory for a struct containing a vector, but not calling placement operator new on that memory. To anyone else finding this question like I did, look for any source of a zeroed but not constructed vector.)

Upvotes: 10

Related Questions