Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the order of destruction of these function-local static objects NOT the inverse of their order of initialization?

I have two function-local static objects, One and Two. One's constructor and destructor both access Two through GetTwo():

#include <iostream>

struct One;
struct Two;

const One& GetOne();
const Two& GetTwo();

struct Two {
  const char* value = "It's two!";
  Two() { std::cout << "Two construct" << std::endl; }
  ~Two() { std::cout << "Two destruct" << std::endl; }
};

struct One {
  One() {
    std::cout << "One construct" << std::endl;
    const char* twoval = GetTwo().value;
    std::cout << "twoval is: " << twoval << std::endl;
  }
  ~One() {
    std::cout << "One destruct" << std::endl;
    const char* twoval = GetTwo().value;
    std::cout << "twoval is: " << twoval << std::endl;
  }
};

const One& GetOne() {
  static One one;
  return one;
}

const Two& GetTwo() {
  static Two two;
  return two;
}

int main(void) {
  GetOne();
}

I compile this with g++ 4.8.4: g++ -std=c++11 [filename]

And it outputs:

One construct
Two construct
twoval is: It's two!
One destruct
twoval is: It's two!
Two destruct

They are constructed and destructed in the same order! I read that for static variables of C++ classes in the same translation unit, the order of destruction is always the inverse of the order of construction. But I guess not? Or, is this undefined behavior?

Also, I heard that for C++11, the C++ committee added some fancy guarantees for function-local static variables, like thread safety. If not undefined, then is this behavior a part of those guarantees? (Which would be pretty nice, since it would stop you from shooting yourself in the foot with One's destructor using a destructed instance of Two.) And what's guaranteed if GetOne and GetTwo are in different translation units?

EDIT:

Thanks for the comments so far, I see now that an object is considered constructed only after its constructor returns, not when it is first entered, so Two is actually constructed before One.

Also I've attempted to read the standard and found this in the C++11 standard, section 6.7 item 4:

The zero-initialization (8.5) of all block-scope variables with static storage duration (3.7.1) or thread storage duration (3.7.2) is performed before any other initialization takes place. Constant initialization (3.6.2) of a block-scope entity with static storage duration, if applicable, is performed before its block is first entered. ...such a variable is initialized the first time control passes through its declaration; such a variable is considered initialized upon the completion of its initialization.

And for destruction, 6.7 points us to 3.6.3, which says:

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.

So if I'm reading this right: For function-local static objects, their construction is "sequenced" at runtime, based on the order the functions are called. And, no matter what translation unit they are defined in, they will be destructed in the inverse of that runtime-dependent order.

Does this sound right? That would make this a nice solution to the static order initialization fiasco. That said, I think you could still shoot yourself in the foot with the code below:

#include <iostream>

struct One;
struct Two;

const One& GetOne();
const Two& GetTwo();
void PrintOneValue(const One& one);

struct Two {
  Two() { std::cout << "Two construct" << std::endl; }
  ~Two() {
    std::cout << "start Two destruct" << std::endl;
    PrintOneValue(GetOne());
    std::cout << "end Two destruct" << std::endl;
  }
};

struct One {
  const char* value = "It's one!";
  One() {
    std::cout << "start One construct" << std::endl;
    GetTwo();
    std::cout << "end One construct" << std::endl;
  }
  ~One() {
    std::cout << "One destruct" << std::endl;
  }
};

void PrintOneValue(const One& one) {
  std::cout << "One's value is: " << one.value << std::endl;
}

const One& GetOne() {
  static One one;
  return one;
}

const Two& GetTwo() {
  static Two two;
  return two;
}

int main(void) {
  GetOne();
}

Which outputs:

start One construct
Two construct
end One construct
One destruct
start Two destruct
One's value is: It's one!
end Two destruct

It accesses One's data after it is destructed, so undefined behavior. But at least it's deterministic.

like image 397
bberg Avatar asked Jul 16 '15 00:07

bberg


2 Answers

The actual Standard text in C++14 [basic.start.term] is:

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. [Note: This definition permits concurrent destruction. —end note ]

In your code, two is constructed during the constructor of one. Therefore the completion of constructor of two is sequenced-before the completion of constructor of one.

So the completion of the destructor of one is sequenced-before the completion of the destructor of two, which explains what you are seeing.

like image 73
M.M Avatar answered Sep 23 '22 08:09

M.M


Change your ctor to:

  One() {
    std::cout << "Start One construct" << std::endl;
    const char* twoval = GetTwo().value;
    std::cout << "twoval is: " << twoval << std::endl;
    std::cout << "Finish One construct" << std::endl;
  }

now you'll see that Two finishes construction before One does. So Two gets registered to be destroyed before One does, and gets destroyed after, because it was actually constructed (completely) first.

Start One construct
Two construct
twoval is: It's two!
Finish One construct
One destruct
twoval is: It's two!
Two destruct
like image 28
Yakk - Adam Nevraumont Avatar answered Sep 25 '22 08:09

Yakk - Adam Nevraumont