0

According to [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 ([basic.types.general]) 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. If multiple such sets of objects would give the program defined behavior, it is unspecified which such set of objects is created.

it can choose not to create objects if that would make the program legal.

Consider the following code (from this question):

void* p = operator new(1);
new (p) char;
delete static_cast<char*>(p);

operator new implicitly creates objects, according to [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.

It also follows [intro.object]/10, so consider two options:

  1. Implicitly creates a char object. See this answer.
  2. Does not create a char object. First, p points to uninitialized memory allocated by operator new. Then, explicitly create a char object on it by placement new. Finally, it is used on delete-expression.

The question is whether option 2 is legal, that is, whether p automatically points to the object created by placement new.

The standard rules at [basic.life]/8:

If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if the original object is transparently replaceable (see below) by the new object.

But no object occupies the memory pointed to by p. Therefore, no replacement takes place, [basic.life]/8 doesn't apply in this case.

I didn't find a stipulation in the standard. So, does the standard allow option 2?

6
  • I believe you mayhave misunderstood "zero" here. It is a count of how many objects are created in the allocated space. For instance, for struct s {int a;};, the implicitly created objects will be the s itself and its int member, the set {s object , int object} is created. From a formal standpoint, the empty set {} is permissible, but can only be created when it gives well defined results. Commented Aug 20, 2022 at 13:31
  • @StoryTeller - Unslander Monica So I'm asking if not creating objects gives a well-defined result. Commented Aug 20, 2022 at 13:36
  • What is the difference in the observable effect of both options? Commented Aug 20, 2022 at 13:44
  • @Daniel I know they are the same, I'm just asking if option 2 is possible. Commented Aug 20, 2022 at 13:51
  • 1
    It can, and what of it? You surmised why the verbiage about replacement doesn't apply. Which set gets created is unspecified and as others noted, the resultant observable behavior is the same... What problem should the standard strive to solve here, by whatever stipulation? Commented Aug 20, 2022 at 13:58

2 Answers 2

3

it can choose not to create objects if that would make the program legal.

Um, no; it cannot. The literal text you quoted says "that operation implicitly creates and starts the lifetime of ... if doing so would result in the program having defined behavior." There is no conditional here, no choice about it. So if the program would otherwise have undefined behavior, and creating a particular object type would give it defined behavior, IOC will do that.

It is not optional; if it is available, it must happen.

Option 1 is not only possible, it is required. It is required because option 2 is undefined behavior.

Let's take it step by step.

void* p = operator new(1);

If IOC does not happen, then p does not point to an object. It merely points to memory. That's just what operator new does.

new (p) char;

This creates a char object in the storage pointed to by p. However, this does not cause p to point to that object. I can't cite a portion of the standard because the standard doesn't say what doesn't happen. [expr.new] explains how new expressions work, and nothing there, or in the definition of the placement-new functions, says that it changes the nature of the argument that it is given.

Therefore, p continues to point at storage and not an object in that storage.

delete static_cast<char*>(p);

This is where UB happens. [expr.delete]/11 tells us:

For a single-object delete expression, the deleted object is the object denoted by the operand if its static type does not have a virtual destructor, and its most-derived object otherwise.

Well, p does not denote an object at all. It just points to storage. static_cast<char*>(p) does not have the power to reach into that storage and get a pointer to an object in that storage. At least, not unless the object is pointer-interconvertible with the object pointed to by p. But since p doesn't point to an object at all at this point... the cast cannot change this.

Therefore, we have achieved undefined behavior.

However, this is where IOC gets triggered. There is a way for IOC to fix this. If operator new(1) performed implicit object creation, and that object would be a char, then this expression would return a pointer to that char. This is what the "pointer to a suitable created object" wording you cited means.

In this case, p points to an actual object of type char.

Then we create a new char, which overlays the original char. Because of that, [basic.life]/8 is triggered. This defines a concept called "transparently replaceable". The new object transparently replaces the old one, and therefore, pointers/references to the old object transparently become pointers/references to the new one:

a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object

Therefore, from this point onward, p points to the new char.

So the static_cast will now return a char* that points to a char, which delete can delete with well-defined behavior. Since the code otherwise would have UB and IOC can fix that UB... it must do so.


Now, if your example had been:

void* p = operator new(1);
auto p2 = new (p) char;
delete p2;

Does an object get created in p? The answer is... it doesn't matter. Because you never use p in a way that requires it to point to a created object, this program has well-defined behavior either way. So the question is irrelevant.

Sign up to request clarification or add additional context in comments.

Comments

0

In the case where the observable effect of both options is the same, both are permitted because of the “as-if rule”:

Allows any and all code transformations that do not change the observable behavior of the program.

The as-if rule

14 Comments

But they wouldn't be the same. If no object is created in that storage, delete static_cast<char*>(p); doesn't work because p doesn't point to the object created by new(p).
@Blackteahamburger: Expr.delete/11: "For a single-object delete expression, the deleted object is the object denoted by the operand if its static type does not have a virtual destructor, and its most-derived object otherwise." If no object was implicitly created, then p does not point to a "suitable created object". It does not point to an object at all. So p does not "denote" an object, nor can a cast expression alone make it point to an object.
@Blackteahamburger: Because that's not how pointers work. Pointers are not just addresses; they point to objects. And those objects have to exist. That's why operator new is said to return a pointer to a "suitable created object".
@Blackteahamburger: Transparently replaceable only applies to objects. And if there is no IOC, then p doesn't point to an object.
@Blackteahamburger: Or maybe you could just not worry about it. This is only a problem because you decided to ignore the return value of new (p) char instead of using it.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.