44

The default placement new operator is declared in 18.6 [support.dynamic] ¶1 with a non-throwing exception-specification:

void* operator new (std::size_t size, void* ptr) noexcept;

This function does nothing except return ptr; so it is reasonable for it to be noexcept, however according to 5.3.4 [expr.new] ¶15 this means that the compiler must check it doesn't return null before invoking the object's constructor:

-15-
[Note: unless an allocation function is declared with a non-throwing exception-specification (15.4), it indicates failure to allocate storage by throwing a std::bad_alloc exception (Clause 15, 18.6.2.1); it returns a non-null pointer otherwise. If the allocation function is declared with a non-throwing exception-specification, it returns null to indicate failure to allocate storage and a non-null pointer otherwise. —end note] If the allocation function returns null, initialization shall not be done, the deallocation function shall not be called, and the value of the new-expression shall be null.

It seems to me that (specifically for placement new, not in general) this null check is an unfortunate performance hit, albeit small.

I've been debugging some code where placement new was being used in a very performance-sensitive code path to improve the compiler's code generation and the check for null was observed in the assembly. By providing a class-specific placement new overload that is declared with a throwing exception-specification (even though it can't possibly throw) the conditional branch was removed, which also allowed the compiler to generate smaller code for the surrounding inlined functions. The result of saying the placement new function could throw, even though it couldn't, was measurably better code.

So I've been wondering whether the null check is really required for the placement new case. The only way it can return null is if you pass it null. Although it's possible, and apparently legal, to write:

void* ptr = nullptr;
Obj* obj = new (ptr) Obj();
assert( obj == nullptr );

I can't see why that would be useful, I suggest it would be better if the programmer had to check for null explicitly before using placement new e.g.

Obj* obj = ptr ? new (ptr) Obj() : nullptr;

Has anyone ever needed placement new to correctly handle the null pointer case? (i.e. without adding an explicit check that ptr is a valid memory location.)

I'm wondering whether it would be reasonable to forbid passing a null pointer to the default placement new function, and if not whether there is some better way to avoid the unnecessary branch, other than trying to tell the compiler the value is not null e.g.

void* ptr = getAddress();
(void) *(Obj*)ptr;   // inform the optimiser that dereferencing pointer is valid
Obj* obj = new (ptr) Obj();

Or:

void* ptr = getAddress();
if (!ptr)
  __builtin_unreachable();  // same, but not portable
Obj* obj = new (ptr) Obj();

N.B. This question is intentionally tagged micro-optimisation, I am not suggesting that you go around overloading placement new for all your types to "improve" performance. This effect was noticed in a very specific performance-critical case and based on profiling and measurement.

Update: DR 1748 makes it undefined behaviour to use a null pointer with placement new, so compilers are no longer required to do the check.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • Ah, sorry for the concurrent edit. In your expression with the conditional operator, didn't you forget the `(ptr)` part of placement new? – R. Martinho Fernandes Jul 10 '13 at 12:55
  • I did, and didn't notice your edit - I've put it back again - thanks! – Jonathan Wakely Jul 10 '13 at 12:57
  • Surely if the null-check in the creation/destruction of these objects is noticeable, then it seems like there's something wrong with the approach in general.... – Mats Petersson Jul 10 '13 at 12:58
  • @MatsPetersson, possibly yes, but previous profiling had found that placement new was the optimum approach in this code, and it turned out that removing the null check made it even better. Requiring the compiler to check the pointer seems like a violation of the "you don't pay for what you don't use" rule to me. – Jonathan Wakely Jul 10 '13 at 13:00
  • `The result of saying the placement new function could throw, even though it couldn't, was measurably better code.` Measurably in terms of code size or in terms of execution time? Shouldn't branch prediction account for the 100% true check, making the execution time overhead negligible? – Arne Mertz Jul 10 '13 at 13:00
  • @ArneMertz, yes, but removing the always-true branch affected the generated code around it which made the whole thing smaller in terms of code size, and keeping as much of this code in the cache as possible is important and has a measurable impact on execution time – Jonathan Wakely Jul 10 '13 at 13:05
  • My point was rather that you may want to look why you have so many calls to new that it makes such a big impact on the profile. – Mats Petersson Jul 10 '13 at 13:07
  • @MatsPetersson, Ah I see. Well the code needs to create and destroy things :) Small changes in this code size can result in cache misses that have a measurable effect. The question is supposed to be about whether the null-check should really be mandated by the language, not this specific code. – Jonathan Wakely Jul 10 '13 at 13:14
  • 1
    how does checking for null before using placement new improve things compared to having placement new perform that check before calling the constructor ? It's the same check - just at a different moment. Either you have the null check somewhere, or you risk calling the constructor for null. The standard tries to avoid the latter. – Sander De Dycker Jul 10 '13 at 13:26
  • 2
    @SanderDeDycker the difference is you can leave the check away before calling placement new if you *know* the pointer is not null. And since one of the philosophies of the standard is "don't pay for what you don't need", I'd consider the unconditionally enforced check a defect in the standard. – Arne Mertz Jul 10 '13 at 13:30
  • 1
    @ArneMertz, JonathanWakely : probably taking this a bit too far, but the only way the programmer can know the pointer is not null, is if the code guarantees it can't be null (eg. : statically allocated, result of a calculation, a prior check exists in the code, ...). In all these cases, the compiler can also figure out it can't be null (the same way the programmer can), and could presumably optimize out the null check before the constructor call. I don't have any examples of compilers going this far, but doing so would combine the safety angle of the standard with your performance angle. – Sander De Dycker Jul 10 '13 at 13:45
  • @SanderDeDycker, how can the compiler tell that `getAddress()` in the code above doesn't return null? – Jonathan Wakely Jul 10 '13 at 13:47
  • @JonathanWakely : how can you tell ? – Sander De Dycker Jul 10 '13 at 13:48
  • 2
    @SanderDeDycker, maybe because the function calls `abort()` if it can't get memory, but it's defined in a different translation unit and I'm not using LTO. How does the compiler know everything I know? Was it written by the NSA? – Jonathan Wakely Jul 10 '13 at 13:49
  • @JonathanWakely : fair point. I said I was taking it a bit too far heh :) – Sander De Dycker Jul 10 '13 at 13:56
  • It looks like you have to check the pointer before calling placement new, in my compiler (gcc 6.2) this gives a segmentation fault. `new (nullptr) double(8.);`. In other words as soon as you have the target address you should check. Or if you want to make it a no-op `if(ptr) new(ptr) double(8.)`. Looks like checking the result of `new(ptr)` is already too late. – alfC Jul 22 '17 at 15:37

1 Answers1

14

While I can't see much of a question in there except "Has anyone ever needed placement new to correctly handle the null pointer case?" (I haven't), I think the case is interesting enough to spill some thoughts on the issue.

I consider the standard broken or incomplete wrt the placement new function and requirements to allocation functions in general.

If you look closely at the quoted §5.3.4,13, it implies that every allocation function has to be checked for a returned nullpointer, even if it is not noexcept. Therefore, it should be rewritten to

If the allocation function is declared with a non-throwing exception-specification and returns null, initialization shall not be done, the deallocation function shall not be called, and the value of the new-expression shall be null.

That would not harm the validity of allocation functions throwing exceptions, since they have to obey §3.7.4.1:

[...] If it is successful, it shall return the address of the start of a block of storage whose length in bytes shall be at least as large as the requested size. [...] The pointer returned shall be suitably aligned so that it can be converted to a pointer of any complete object type with a fundamental alignment requirement (3.11) and then used to access the object or array in the storage allocated (until the storage is explicitly deallocated by a call to a corresponding deallocation function).

And §5.3.4,14:

[ Note: when the allocation function returns a value other than null, it must be a pointer to a block of storage in which space for the object has been reserved. The block of storage is assumed to be appropriately aligned and of the requested size. [...] -end note ]

Obviously, a placement new that just returns the given pointer, cannot reasonably check avilable storage size and alignment. Therefore,

§18.6.1.3,1 about placement new says

[...] The provisions of (3.7.4) do not apply to these reserved placement forms of operator new and operator delete.

(I guess they missed to mention §5.3.4,14 at that place.)

However, together these paragraphs say indirectly "if you pass a garbage pointer to the palcement functions, you get UB, because §5.3.4,14 is violated". So it's up to you to check the sanity of any poitner given to placement new.

In that spirit, and with the rewritten §5.3.4,13, the standard could strip the noexcept from placement new, leading to an addition to that indirect conclusion: "...and if you pass null, you get UB as well". On the other hand, its much less likely to have a misaligned pointer or pointer to too few memory than having a null pointer.

However, this would remove the need for checking against null, and it would fit well to the philosophy "don't pay for what you don't need". The allocation function itself would not need to check, because §18.6.1.3,1 explicitly says so.

To round things up, one could consider adding a second overload

 void* operator new(std::size_t size, void* ptr, const std::nothrow_t&) noexcept;

Sadly, proposing this to the committee is unlikely to result in a change, because it would break existing code relying on placement new being ok with null pointers.

Arne Mertz
  • 24,171
  • 3
  • 51
  • 90
  • Thanks for the insightful analysis. I'll leave it a day or two longer in case anyone else has anything to add, but I expect I'll be accepting this. – Jonathan Wakely Jul 11 '13 at 23:46