简体   繁体   中英

Is it UB to access a non-existent object?

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?

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.

If it compiles with a compliant compiler, then clearly it is 'legal'. Whether it is useful, well defined, or otherwise is the practical issue of concern rather than legality. C++ code can be semantically incorrect whilst being syntactically valid.

The issue here is that the std::string constructor is never called on static_cast<std::string*>(p) , so that when std::string::operator==() is invoked the behaviour is undefined, and will depend on whatever non-deterministic content p happens to point to. Since the actual string space is itself dynamically allocated, any string operation will likely fail or corrupt the heap. It will result in serious runtime errors.

You should explicitly invoke the constructor using placement new to construct an object over a byte block. .

std::string* s = new(p) std::string( "string" ) ; 

So that while s and p refer to the same memory, the constructor will have been called through the creation of s .

That said the benefit of placement new for an object that internally dynamically allocates memory from the heap is questionable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM