Lemon Drop
Lemon Drop

Reputation: 2133

Assignment to a non-existent object in C++

Is it legal to assign to memory which does not have an object allocated in it? I ask this as in [expr.ass]/2 it says:

In simple assignment (=), the object referred to by the left operand is modified ([defns.access]) by replacing its value with the result of the right operand.

Which combined with [defns.access] do seem to imply that the left operand should refer to an actual object. An example which could trigger this would be allocating raw memory without any object allocated in it and then assigning to it:

int * foo = static_cast<int *>(::operator new(sizeof(int)));
*foo = 5;

This seems natural but I feel based on that set of definitions it may be illegal without first calling placement new for example on the allocated memory to allocate the object. Additionally I am not sure if that cast is valid without actual int objects having been created there yet, so that might be an issue with that example too.

Similarly, would preforming this action through an "imaginary" object representation like so be valid:

void * foo = ::operator new(sizeof(int));
int bar = 5;
std::memcpy(foo, &bar, sizeof(int));

This seems like it could be, but based on [basic.types]/3 as it says:

For any trivially copyable type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a potentially-overlapping subobject, if the underlying bytes ([intro.memory]) making up obj1 are copied into obj2, obj2 shall subsequently hold the same value as obj1.

Which seems to imply that again the destination needs to be an actual allocated object still and that this wouldn't work either.

I suppose the final confusion if neither of those examples are valid is how does something like std::memset work? Presumably it is doing the same, taking empty memory with nothing in it and then interpreting it through some arbitrary object representation to assign to. In fact the C11 standard says the following about it:

The memset function copies the value of c (converted to an unsigned char) into each of the first n characters of the object pointed to by s.

Which suggests that there has to be an object in the destination (s) already existing there. In the context of C as well from my understanding an object is created when something like just a void * pointing at empty memory is casted to a pointer of another type and accessed, so wouldn't memset in C create a bunch of char objects which later would be incorrectly aliased to another type if casted to say float *? I think that might just be an issue with how C object creation works in its own standard being broken in that regard. I also assume the answer in C++ is "just placement new everything first", but it is hard to tell with how complex the standard gets when dealing with fundamental things like this.

Upvotes: 2

Views: 353

Answers (3)

ph3rin
ph3rin

Reputation: 4886

Formally, the word "lifetime" itself has no meaning when there is no object.

[intro.object/1]: The constructs in a C++ program create, destroy, refer to, access, and manipulate objects. An object is created by a definition, by a new-expression, when implicitly changing the active member of a union, or when a temporary object is created ([conv.rval], [class.temporary]). An object occupies a region of storage in its period of construction ([class.cdtor]), throughout its lifetime, and in its period of destruction ([class.cdtor]).

Note that the example you have provided uses the global new operator, not the new-expression. Therefore, you have not created any object, let aside of its lifetime. You cannot memcpy, since there is no object.

Upvotes: 2

eerorika
eerorika

Reputation: 238401

int * foo = static_cast<int *>(::operator new(sizeof(int)));
*foo = 5;

I feel based on that set of definitions it may be illegal without first calling placement new

It would indeed be undefined.

Additionally I am not sure if that cast is valid without actual int objects having been created there yet

Casting from void* to another pointer is always well defined as far as I know. Attempting to access a non-existing object - or object of incompatible type - through such reinterpreted pointer is not valid.

Similarly, would preforming this action through an "imaginary" object representation like so be valid:

void * foo = ::operator new(sizeof(int));
int bar = 5;
std::memcpy(foo, &bar, sizeof(int));

Also undefined.

how does something like std::memset work?

It sets the bit pattern of objects. For example:

int foo = 42;
std::memset(&foo, 0, sizeof foo);

All bytes of foo are now 0. No rule was violated.

std::memset doesn't create any objects just like std::malloc and std::memcpy don't.

TL;DR Use placement new if you want to create objects into allocated bare storage.


P.S There is a proposal (p0593rX) to introduce implicit creation of trivial objects into C++ which would allow similar patterns to what is allowed in C.

Upvotes: 1

Matt Weber
Matt Weber

Reputation: 116

I believe this is a question of lifetime. Given:

int * foo = static_cast<int *>(::operator new(sizeof(int)));

The question is, does *foo refer to a valid object. According to N3797 § 3.8 [basic.life], it does.

The lifetime of an object of type T begins when:
— storage with the proper alignment and size for type T is obtained, and

— if the object has non-trivial initialization, its initialization is complete.

Since int has trivial initialization, simply allocating correctly aligned storage for it begins the lifetime of a valid object of type int. It's certainly true that *foo has an indeterminate value at this point, so observing its value is worthless at best and undefined behavior at worst, but assigning to it is correct.

Similarly, your second example results in foo pointing to a valid object of type int. The subsequent memcpy operation is completely fine.

Upvotes: 1

Related Questions