Is it UB to access a non-existent object?

Viewed 189

There seems to be no more silly question than this. But does the standard allow it?

Consider:

void* p = operator new(sizeof(std::string));
*static_cast<std::string*>(p) = "string";

[basic.life]/6:

Before the lifetime of an object has started but after the storage which the object will occupy has been allocated24 or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released... 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, or

  • the pointer is used as the operand of a static_­cast ([expr.static.cast]), except when the conversion is to pointer to cv void, or to pointer to cv void and subsequently to pointer to cv char, cv unsigned char, or cv std​::​byte ([cstddef.syn]), or

(Note that according to [intro.object]/10, a std::string object is not implicitly created by operator new because it is not of implicit-lifetime type.)

However, [basic.life]/6 does not apply to this code because there are no objects at all.

What am I missing?

4 Answers

static_cast is fine, however dereferencing resulting pointer to (non-existing) std::string object leads to Undefined Behaviour:

7.2.1 Value category [basic.lval]
11 If a program attempts to access (3.1) the stored value of an object through a glvalue whose type is not similar (7.3.5) to one of the following types the behavior is undefined:
(11.1) the dynamic type of the object,
(11.2) a type that is the signed or unsigned type corresponding to the dynamic type of the object, or
(11.3) a char, unsigned char, or std::byte type.
...

Edit:

Quote from the question is not really applicable here, it refers to a situations like this:

struct foo
{
    bar b1;
    bar b2;

    foo(void): b1{&b2}, b2{} {}
};

Is it legal to access a non-existent object?

No, you wrote it yourself:

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

which is exactly what *static_cast<std::string*>(p) = "string"; does. You must construct a std::string before you call the assignment operator:

int main() {
    void* p = operator new(sizeof(std::string));
    std::string* sp = new(p) std::string;        // construct the string
    
    *sp = "string";                              // now assignment is fine
    
    sp->~basic_string();
    operator delete(p);
}

However, [basic.life]/6 does not apply to this code because there are no objects at all.

Yes it's applicable here. Your code has allocated the storage but not started the lifetime of the object and calls a non-static member function of the (non-existing) object.


The confusion seems to stem from the fact that you never start the lifetime of the object and you therefore think that "before the lifetime" doesn't apply. It does. It's "before the lifetime" until you start the lifetime. If you never start the lifetime of the object, it's "before the timetime" during the complete program run.

The standard clause could have said: 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 if the object's lifetime has not been started - and it would mean the same thing.

The behaviour is undefined by omission.

The assignment operator is overloaded, so a member function named operator= is invoked. The standard says

A non-static member function may be called for an object of its class type

There isn't any object of an appropriate type, and there is nothing else in the standard that might be giving meaning to this program.

[intro.object]/10

Some operations are described as implicitly creating objects within a specified region of storage. For each operation that is specified as implicitly creating objects, that operation implicitly creates and starts the lifetime of zero or more objects of implicit-lifetime types in its specified region of storage if doing so would result in the program having defined behavior. If no such set of objects would give the program defined behavior, the behavior of the program is undefined.

[intro.object]/11

Further, after implicitly creating objects within a specified region of storage, some operations are described as producing a pointer to a suitable created object. These operations select one of the implicitly-created objects whose address is the address of the start of the region of storage, and produce a pointer value that points to that object, if that value would result in the program having defined behavior. If no such pointer value would give the program defined behavior, the behavior of the program is undefined.

[intro.object]/13

Any implicit or explicit invocation of a function named operator new or operator new[] implicitly creates objects in the returned region of storage and returns a pointer to a suitable created object.

If an std::string[1] (or std::string[1][1] etc.) object were created, and a pointer to the std::string subobject were produced by operator new(sizeof(std::string)), then *static_cast<std::string*>(p) = "string" would have undefined behavior per [basic.life]/(7.2)

the glvalue [denoting an out-of-lifetime object] is used to call a non-static member function of the object

If operator new(sizeof(std::string)) produced a pointer to object of some other type (like int or double), then undefined behavior would be triggered by [expr.ref]/8:

If E2 is a non-static member and the result of E1 is an object whose type is not similar to the type of E1, the behavior is undefined.

So, there is no set of objects which would give the program defined behavior. Thus, the highlighted sentence of [intro.object]/10 apply here.

Related