2

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?

user4581301
  • 33,082
  • 7
  • 33
  • 54
getsoubl
  • 808
  • 10
  • 25
  • 3
    Perhaps an object allocated with `malloc` (rather than `new`)? Storage has then been allocated but no object actually exists as the constructor has not been called. – Paul Sanders Dec 12 '22 at 21:22
  • 1
    Or a byte array (of the appropriate size and alignment). Beware of the [implicit lifetime types](https://en.cppreference.com/w/cpp/language/lifetime#Temporary_object_lifetime) though. – HolyBlackCat Dec 12 '22 at 21:27

2 Answers2

4

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
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
1

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.)

user17732522
  • 53,019
  • 2
  • 56
  • 105