Home > OS >  Object Lifetime and undefined behavior
Object Lifetime and undefined behavior

Time:12-13

In the 3.8 Object lifetime, there is the following section

Before the lifetime of an object has started but after the storage which the object will occupy has been allocated40 or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that refers to the storage location where the object will be or was located may be used but only in limited ways. For an object under construction or destruction, see 12.7. Otherwise, such a pointer refers to allocated storage (3.7.4.2), and using the pointer as if the pointer were of type void*, is well-defined. Indirection through such a pointer is permitted but the resulting lvalue may only be used in limited ways, as described below. The program has undefined behavior if:

the pointer is used to access a non-static data member or call a non-static member function of the object,

Can anyone provide an example that explains the above statement?

CodePudding user response:

Here's an example with comments:

#include <cstdlib>
#include <iostream>
#include <string>

int main() {
    void* storage = std::malloc(sizeof(std::string));
    
    // Before the lifetime of the object has started but after the
    // storage which the object will occupy has been allocated

    // starting the lifetime of the object:
    std::string* a_string_ptr = new(storage) std::string;

    *a_string_ptr = "Hello world, what is going on?";
    std::cout << *a_string_ptr << '\n';

    // ending the lifetime of the object
    a_string_ptr->~basic_string();
    
    // after the lifetime of the object has ended and before the storage
    // which the object occupied is reused or released

    std::free(storage);

    // after the storage which the object occupied is released
}

CodePudding user response:

Some examples (there are many other ways):

struct A {
    void f() {};
};

int main() {
    A a = ((&a)->f(), A{}); //1
    a.~A();
    (&a)->f(); //2

    A b = (*&b, A{}); //3
    b.~A();
    (*&b, 0); //4
}

In all four labeled cases the pointer formed with & and dereferenced is not an invalid, because these evaluations happen during the storage duration of the a and b object.

But in //1 the call to f is made before the initialization of a is complete, meaning before its lifetime begins, and before its constructor starts, so that the quoted paragraph applies.

And in //2 the call to f is made after the destructor of a was called, meaning that its lifetime has already ended, so that the quoted paragraph also applies.

So both //1 and //2 have undefined behavior.

Contrary, //3 and //4 are fine. They form and dereference a pointer outside the lifetime, but do not run afoul of any of the rules in [basic.life] for glvalues, after the paragraph you quoted. In particular no access to a scalar is performed, nor is a non-static member accessed.


However, the quoted restriction is probably redundant with the later restrictiona on use of glvalues outside of lifetime, which also forbid calling non-static member functions or accessing non-static data members. But the only way to do any of that through a pointer is to first dereference the pointer to get a glvalue anyway. (That's what * and -> are defined to do.)

  • Related