Consider the following code snippet constructing an instance of a POD (plain old data) struct in-place:
#include <new>
#include <cassert>
#include <cstddef>
struct Test
{
int a;
char b;
double c;
};
int main()
{
const std::size_t minimumNumberOfBytes = sizeof( Test ) * 4;
// Get a block of memory that can accommodate a Test instance and then some!
void* const ptrToMemBlock = new char[ minimumNumberOfBytes ];
assert( ptrToMemBlock );
// Construct a Test instance in-place.
const Test* const testInstance( ::new ( ptrToMemBlock ) Test() );
// Is this assumption guaranteed to be true?
assert( testInstance == ptrToMemBlock );
}
Is the assumption represented by the final assert() guaranteed to always be correct? Or is it conceivable that the compiler might decide to construct the Test instance, say a few bytes after the start of the memory block I specified in the placement-new call?
Note that I'm asking specifically about POD types here. I know that things can get iffy if multiple inheritance and stuff like that gets involved.
This assertion will always hold, because new is required to return blocks of memory with MAXIMUM possible alignment. BTW - your first assert() is worthless, as normal new does not return nullptr - it throws or aborts, only "nothrow new" can return nullptr.
Yes, the the last assert is guaranteed to hold, because this form of placement-new must always return the passed pointer, not using any space for itself:
5.3.4 New [expr.new]
8 A new-expression may obtain storage for the object by calling an allocation function (3.7.4.1). [...]
10 An implementation is allowed to omit a call to a replaceable global allocation function (18.6.1.1, 18.6.1.2). When it does so, the storage is instead provided by the implementation or provided by extending the allocation of another new-expression. [...]
11 When a new-expression calls an allocation function and that allocation has not been extended, the newexpression passes the amount of space requested to the allocation function as the first argument of type std::size_t. That argument shall be no less than the size of the object being created; it may be greater than the size of the object being created only if the object is an array.
[...]
Your new-expression calls the global placement-new allocation-function.
That is a non-replacable function, thus the allocation cannot be extended or omitted.
Also, you are not allocating an array but a single object, thus no padding of the request may occur at all.
18.6.1.3 Placement forms [new.delete.placement]
1 These functions are reserved, a C++ program may not define functions that displace the versions in the Standard C++ library (17.6.4). The provisions of (3.7.4) do not apply to these reserved placement forms of operator new and operator delete.
void* operator new(std::size_t size, void* ptr) noexcept;
2 Returns: ptr.
3 Remarks: Intentionally performs no other action.
And this guarantees that the allocation-function returns the passed pointer unchanged.
Yes, the assertion will hold. Any new expression creating a single object must request exactly sizeof(Test) bytes of storage from the allocation function; and so it must place the object at the start of that storage in order to have enough room.
Note: This is based on the specification of a new-expression in C++11. It looks like C++14 will change the wording, so the answer may be different in the future.
Related
A placement-new expression that takes a single argument that is a pointer to pre-allocated memory will construct an object of type T in that memory.
Why does it call the standard placement-new operator void* operator new ( std::size_t count, void* ptr ); since the latter does nothing and just returns its pointer argument?
int x = 10;
int* p = new(&x) int{1024};
Could you explain the steps taken in the above by the compiler to construct a new int in the memory address of x?
Why doesn't the placement-new expression directly construct an object at the memory address it gets as a pointer, rather than calling an operator function that does nothing and just returns its pointer argument?
The general rule is that new (args...) T will call operator new(sizeof(T), args...), and this function is required to return void*. If this operator new call returns successfully, the object is then constructed into the memory pointed to by the return value.
This general rule is powerful enough to support both the ordinary new expression new int and the placement form new (&x) int without any special cases. These two expressions call different overloads of operator new, which is why the former allocates and the latter does not. No matter what, an object is constructed at the end (unless the operator new function failed by throwing an exception).
There is no need to have a special rule in the language that says operator new is not called by a placement new expression. Instead, the compiler can simply optimize the code by directly constructing the int object into &x without calling operator new first, since it already knows that the placement operator new will just return its second argument.
(Actually, the reality is a bit more complicated than this. If T is an array type, then operator new[] is called instead of operator new, and the compiler may request from operator new[] a greater amount of memory than the array will actually occupy, and adjust the returned pointer before constructing the array. There are also special rules relating to over-aligned types, and there actually is some special-casing for placement new and delete. These details are not relevant to this answer.)
Arrays of any type are implicit-lifetime objects, and it is possible to to begin the lifetime of implicit-lifetime object, without beginning the lifetime of its subobjects.
As far as I am aware, the possibility to create arrays without beginning the lifetime of their elements in a way that doesn't result in UB, was one of the motivations for implicit-lifetime objects, see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0593r6.html.
Now, what is the proper way to do it? Is allocating memory and returning a pointer to array is enough? Or there is something else one needs to be aware of?
Namely, is this code valid and does it create an array with uninitialized members, or we still have UB?
// implicitly creates an array of size n and returns a pointer to it
auto arrPtr = reinterpret_cast<T(*)[]>(::operator new(sizeof(T) * n, std::alignval_t{alignof(T)}) );
// is there a difference between reinterpret_cast<T(*)[]> and reinterpret_cast<T(*)[n]>?
auto arr = *arrPtr; // de-reference of the result in previous line.
The question can be restated as follows.
According to https://en.cppreference.com/w/cpp/memory/allocator/allocate, the allocate function function creates an array of type T[n] in the storage and starts its lifetime, but does not start lifetime of any of its elements.
A simple question - how is it done? (ignoring the constexpr part, but I wouldn't mind if constexpr part is explained in the answer as well).
PS: The provided code is valid (assuming it is correct) for c++20, but not for earlier standards as far as I am aware.
I believe that an answer to this question should answer two similar questions I have asked earlier as well.
Arrays and implicit-lifetime object
creation.
Is it possible to allocatate uninialized array in a way that does
not result in
UB.
EDIT: I am adding few code snippets, to make my question more clear. I would appreciate an answer explaining which one are valid and which ones are not.
PS: feel free to replace malloc with aligned version, or ::operator new variation. As far as I am aware it doesn't matter.
Example #1
T* allocate_array(std::size_t n)
{
return reinterpret_cast<T*>( malloc(sizeof(T) * n) );
// does it return an implicitly constructed array (as long as
// subsequent usage is valid) or a T* pointer that does not "point"
// to a T object that was constructed, hence UB
// Edit: if we take n = 1 in this example, and T is not implicit-lifetime
// type, then we have a pointer to an object that has not yet been
// constructed and and doesn't have implicit lifetime - which is bad
}
Example #2.
T* allocate_array(std::size_t n)
{
// malloc implicitly constructs - reinterpet_cast should a pointer to
// suitably created object (a T array), hence, no UB here.
T(*)[] array_pointer = reinterpret_cast<T(*)[]>(malloc(sizeof(T) * n) );
// The pointer in the previous line is a pointer to valid array, de-reference
// is supposed to give me that array
T* array = *array_pointer;
return array;
}
Example #3 - same as 2 but size of array is known.
T* allocate_array(std::size_t n)
{
// malloc implicitly constructs - reinterpet_cast should a pointer to
// suitably created object (a T array), hence, no UB here.
T(*)[n] n_array_pointer = reinterpret_cast<T(*)[n]>(malloc(sizeof(T) * n) );
// The pointer in the previous line is a pointer to valid array, de-reference
// is supposed to give me that array
T* n_array = *n_array_pointer;
return n_array;
}
Are any of these valid?
The answer
While wording of the standard is not 100% clear, after reading the paper more carefully, the motivation is to make casts to T* legal and not casts to T(*)[]. Dynamic construction of arrays. Also, the changes to the standard by the authors of the paper imply that the cast should be to T* and not to T(*)[]. Hence, the accepting the answer by Nicol Bolas as the correct answer for my question.
The whole point of implicit object creation is that it is implicit. That is, you don't do anything to get it to happen. Once IOC occurs on a piece of memory, you may use the memory as if the object in question exists, and so long as you do that, your code works.
When you get your T* back from allocator_traits<>::allocate, if you add 1 to the pointer, then the function has returned an array of at least 1 element (the new pointer could be the past-the-end pointer for the array). If you add 1 again, then the function has returned an array of at least 2 elements. Etc. None of this is undefined behavior.
If you do something inconsistent with this (casting to a different pointer type and acting as though there is an array there), or if you act as though the array extends beyond the size of the storage that IOC applies to, then you get UB.
So allocator_traits::allocate doesn't really have to do anything, so long as the memory that the allocator allocated implicitly creates objects.
// does it return an implicitly constructed array (as long as
// subsequent usage is valid) or a T* pointer that does not "point"
// to a T object that notconstructed, hence UB
Neither. It returns a pointer (to type T) to storage into which objects may have been implicitly created already. Which objects have been implicitly created depends on how you use this storage. And merely doing a cast doesn't constitute "using" the storage.
It isn't the reinterpret_cast that causes UB; it's using the pointer returned by an improper reinterpret_cast that's the problem. And since IOC works based on the operation that would have caused UB, IOC doesn't care what you cast the pointer to.
Part and parcel of the IOC rules is the corollary "suitable created object" rule. This rule says that certain operations (like malloc and operator new) return a pointer to a "suitable created object". Essentially it's back to quantum superposition: if IOC retroactively creates an object to make your code work, then these functions retroactively returns a pointer to whichever object that was created that makes your code work.
So if your code uses the pointer as a T* and does pointer arithmetic on that pointer, then malloc returned a pointer to the first element of an array of Ts. How big is that array? That depends: how big was the allocation, and how far did you do your pointer arithmetic? Does it have live Ts in them? That depends: do you try to access any Ts in the array?
In C++, is this code correct?
#include <cstdlib>
#include <cstring>
struct T // trivially copyable type
{
int x, y;
};
int main()
{
void *buf = std::malloc( sizeof(T) );
if ( !buf ) return 0;
T a{};
std::memcpy(buf, &a, sizeof a);
T *b = static_cast<T *>(buf);
b->x = b->y;
free(buf);
}
In other words, is *b an object whose lifetime has begun? (If so, when did it begin exactly?)
This is unspecified which is supported by N3751: Object Lifetime, Low-level Programming, and
memcpy which says amongst other things:
The C++ standards is currently silent on whether the use of memcpy to
copy object representation bytes is conceptually an assignment or an
object construction. The difference does matter for semantics-based
program analysis and transformation tools, as well as optimizers,
tracking object lifetime. This paper suggests that
uses of memcpy to copy the bytes of two distinct objects of two different trivial copyable tables (but otherwise of the same size) be
allowed
such uses are recognized as initialization, or more generally as (conceptually) object construction.
Recognition as object construction will support binary IO, while still
permitting lifetime-based analyses and optimizers.
I can not find any meeting minutes that has this paper discussed, so it seems like it is still an open issue.
The C++14 draft standard currently says in 1.8 [intro.object]:
[...]An object is created by a definition (3.1), by a new-expression
(5.3.4) or by the implementation (12.2) when needed.[...]
which we don't have with the malloc and the cases covered in the standard for copying trivial copyable types seem to only refer to already existing objects in section 3.9 [basic.types]:
For any object (other than a base-class subobject) of trivially
copyable type T, whether or not the object holds a valid value of type
T, the underlying bytes (1.7) making up the object can be copied into
an array of char or unsigned char.42 If the content of the array of
char or unsigned char is copied back into the object, the object shall
subsequently hold its original value[...]
and:
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
base-class subobject, if the underlying bytes (1.7) making up obj1 are
copied into obj2,43 obj2 shall subsequently hold the same value as
obj1.[...]
which is basically what the proposal says, so that should not be surprising.
dyp points out a fascinating discussion on this topic from the ub mailing list: [ub] Type punning to avoid copying.
Propoal p0593: Implicit creation of objects for low-level object manipulation
The proposal p0593 attempts to solve this issues but AFAIK has not been reviewed yet.
This paper proposes that objects of sufficiently trivial types be created on-demand as necessary within newly-allocated storage to give programs defined behavior.
It has some motivating examples which are similar in nature including a current std::vector implementation which currently has undefined behavior.
It proposes the following ways to implicitly create an object:
We propose that at minimum the following operations be specified as implicitly creating objects:
Creation of an array of char, unsigned char, or std::byte implicitly creates objects within that array.
A call to malloc, calloc, realloc, or any function named operator new or operator new[] implicitly creates objects in its returned storage.
std::allocator::allocate likewise implicitly creates objects in its returned storage; the allocator requirements should require other allocator implementations to do the same.
A call to memmove behaves as if it
copies the source storage to a temporary area
implicitly creates objects in the destination storage, and then
copies the temporary storage to the destination storage.
This permits memmove to preserve the types of trivially-copyable objects, or to be used to reinterpret a byte representation of one object as that of another object.
A call to memcpy behaves the same as a call to memmove except that it introduces an overlap restriction between the source and destination.
A class member access that nominates a union member triggers implicit object creation within the storage occupied by the union member. Note that this is not an entirely new rule: this permission already existed in [P0137R1] for cases where the member access is on the left side of an assignment, but is now generalized as part of this new framework. As explained below, this does not permit type punning through unions; rather, it merely permits the active union member to be changed by a class member access expression.
A new barrier operation (distinct from std::launder, which does not create objects) should be introduced to the standard library, with semantics equivalent to a memmove with the same source and destination storage. As a strawman, we suggest:
// Requires: [start, (char*)start + length) denotes a region of allocated
// storage that is a subset of the region of storage reachable through start.
// Effects: implicitly creates objects within the denoted region.
void std::bless(void *start, size_t length);
In addition to the above, an implementation-defined set of non-stasndard memory allocation and mapping functions, such as mmap on POSIX systems and VirtualAlloc on Windows systems, should be specified as implicitly creating objects.
Note that a pointer reinterpret_cast is not considered sufficient to trigger implicit object creation.
The code is legal now, and retroactively since C++98!
The answer by #Shafik Yaghmour is thorough and relates to the code validity as an open issue - which was the case when answered. Shafik's answer correctly refer to p0593 which at the time of the answer was a proposal. But since then, the proposal was accepted and things got defined.
Some History
The possibility of creating an object using malloc was not mentioned in the C++ specification before C++20, see for example C++17 spec [intro.object]:
The constructs in a C++ program create, destroy, refer to, access, and manipulate
objects. An object is created by a definition (6.1), by a new-expression (8.5.2.4),
when implicitly changing the active member of a union (12.3), or when a temporary
object is created (7.4, 15.2).
Above wording does not refer to malloc as an option for creating an object, thus making it a de-facto undefined behavior.
It was then viewed as a problem, and this issue was addressed later by https://wg21.link/P0593R6 and accepted as a DR against all C++ versions since C++98 inclusive, then added into the C++20 spec, with the new wording:
[intro.object]
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, by an operation that implicitly creates objects (see below)...
...
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. If multiple such
pointer values would give the program defined behavior, it is
unspecified which such pointer value is produced.
The example given in C++20 spec is:
#include <cstdlib>
struct X { int a, b; };
X *make_x() {
// The call to std::malloc implicitly creates an object of type X
// and its subobjects a and b, and returns a pointer to that X object
// (or an object that is pointer-interconvertible ([basic.compound]) with it),
// in order to give the subsequent class member access operations
// defined behavior.
X *p = (X*)std::malloc(sizeof(struct X));
p->a = 1;
p->b = 2;
return p;
}
As for the use of memcpy - #Shafik Yaghmour already addresses that, this part is valid for trivially copyable types (the wording changed from POD in C++98 and C++03 to trivially copyable types in C++11 and after).
Bottom line: the code is valid.
As for the question of lifetime, let's dig into the code in question:
struct T // trivially copyable type
{
int x, y;
};
int main()
{
void *buf = std::malloc( sizeof(T) ); // <= just an allocation
if ( !buf ) return 0;
T a{}; // <= here an object is born of course
std::memcpy(buf, &a, sizeof a); // <= just a copy of bytes
T *b = static_cast<T *>(buf); // <= here an object is "born"
// without constructor
b->x = b->y;
free(buf);
}
Note that one may add a call to the destructor of *b, for the sake of completeness, before freeing buf:
b->~T();
free(buf);
though this is not required by the spec.
Alternatively, deleting b is also an option:
delete b;
// instead of:
// free(buf);
But as said, the code is valid as is.
From a quick search.
"... lifetime begins when the properly-aligned storage for the object is allocated and ends when the storage is deallocated or reused by another object."
So, I would say by this definition, the lifetime begins with the allocation and ends with the free.
Is this code correct?
Well, it will usually "work", but only for trivial types.
I know you did not ask for it, but lets use an example with a non-trivial type:
#include <cstdlib>
#include <cstring>
#include <string>
struct T // trivially copyable type
{
std::string x, y;
};
int main()
{
void *buf = std::malloc( sizeof(T) );
if ( !buf ) return 0;
T a{};
a.x = "test";
std::memcpy(buf, &a, sizeof a);
T *b = static_cast<T *>(buf);
b->x = b->y;
free(buf);
}
After constructing a, a.x is assigned a value. Let's assume that std::string is not optimized to use a local buffer for small string values, just a data pointer to an external memory block. The memcpy() copies the internal data of a as-is into buf. Now a.x and b->x refer to the same memory address for the string data. When b->x is assigned a new value, that memory block is freed, but a.x still refers to it. When a then goes out of scope at the end of main(), it tries to free the same memory block again. Undefined behavior occurs.
If you want to be "correct", the right way to construct an object into an existing memory block is to use the placement-new operator instead, eg:
#include <cstdlib>
#include <cstring>
struct T // does not have to be trivially copyable
{
// any members
};
int main()
{
void *buf = std::malloc( sizeof(T) );
if ( !buf ) return 0;
T *b = new(buf) T; // <- placement-new
// calls the T() constructor, which in turn calls
// all member constructors...
// b is a valid self-contained object,
// use as needed...
b->~T(); // <-- no placement-delete, must call the destructor explicitly
free(buf);
}
Suppose I have a type template parameter T.
And suppose I have a std::aligned_storage as follows:
typename std::aligned_storage<sizeof(T), alignof(T)>::type storage;
I want to placement new a T into the storage.
What is the standard-compliant pointer value/type to pass to the placement new operator, and how do I derive that from storage?
new (& ???) T(a,b,c);
For example:
new (&storage) T(a,b,c);
new (static_cast<void*>(&storage)) T(a,b,c);
new (reinterpret_cast<T*>(&storage)) T(a,b,c);
new (static_cast<T*>(static_cast<void*>(&storage));
Which of the above (if any) are compliant, and if none, what is the better way?
The most paranoid way is
::new ((void *)::std::addressof(storage)) T(a, b, c);
Explanation:
::std::addressof guards against overloaded unary operator& on storage, which is technically allowed by the standard. (Though no sane implementation would do it.) The ::std guards against any non-top-level namespaces (or classes) called std that might be in scope.
(void *) (which in this case is the equivalent of a static_cast) ensures that you call the placement operator new taking a void * rather than something else like decltype(storage) *.
::new skips any class-specific placement operator news, ensuring that the call goes to the global one.
Together, this guarantees that the call goes to the library placement operator new taking a void *, and that the T is constructed at where storage is.
In most sane programs, though,
new (&storage) T(a,b,c);
should be sufficient.
The placement allocation function is described as follows (C++14 n4140 18.6.1.3):
void* operator new(std::size_t size, void* ptr) noexcept;
Returns: ptr.
Remarks: Intentionally performs no other action.
20.10.7.6 table 57 describes aligned_storage<Len, Align> thus:
The member typedef type
shall be a POD type suitable for use
as uninitialized storage for any object
whose size is at most Len and whose
alignment is a divisor of Align.
This implies that in your case, &storage is suitably aligned for holding an object of type T. Therefore, under normal circumstances1, all 4 ways you've listed of calling placement new are valid and equivalent. I would use the first one (new (&storage)) for brevity.
1 T.C. correctly pointed out in the comments that it is technically possible for your program to declare an overload of the allocation function taking a typename std::aligned_storage<sizeof(T), alignof(T)>::type*, which would then be selected by overload resolution instead of the library-provided 'placement new' version.
I would say this unlikely in at least 99.999% of cases, but if you need to guard against that as well, use one of the casts to void*. The direct static_cast<void*>(&storage) is enough.
Also, if you're paranoid to this level, you should probably use ::new instead of just new to bypass any class-specific allocation functions.
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.
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.