Reputation: 727
A common idiom when constructing buffers (say a ring buffer) for objects of class type T is to initialize a T* object with the address of memory obtained from std::malloc() or operator new(), and then to construct objects in that buffer on demand using placement new, using pointer arithmetic on the T pointer to traverse the block of memory.
While it seems highly unlikely that there is any compiler on which this would not work (it does work with g++ and clang++), it seems to me that strictly speaking this may have undefined behavior. This is because §8.7/4 of C++17 seems only to permit pointer arithmetic on arrays, and a block of memory returned by malloc, operator new or operator new[] is not an array - as I understand it only the new[] expression can create an array in dynamic memory, which will thereby be fully initialized at the point of construction.
This also got me thinking that the reference implementation of std::uninitialized_copy has undefined behaviour for dynamically allocated uninitialized memory, because its reference implementation in §23.10.10.4/1 of C++17 uses pointer arithmetic on the destination iterator, which would here be a pointer.
Arguably the same applies for std::uninitialized_copy if the uninitialized memory is obtained non-dynamically, say using an aligned array of unsigned char or of std::byte as permitted by §4.5/3 of C++17, because the arithmetic in §8.7/4 implies that the destination pointer type upon which the arithmetic is carried out should be that of the array element type (unsigned char or std::byte) and not the type constructed in it using placement new.
This seems surprising. Can anyone point out the flaw (if any) in this reasoning.
Upvotes: 2
Views: 259
Reputation: 22152
Yes, pointer arithmetic on a pointer returned from malloc
or operator new
has undefined behavior without a previous array placement-new (which itself cannot be done reliably) and so does using std::unintialized_copy
on it, which is defined to behave as if this pointer arithmetic was done.
The best you can do is to create a std::byte
(or unsigned char
) array as storage, directly using new[]
, and then placement-new individual objects into that storage array, which will make these objects nested in the buffer array.
Pointer arithmetic on the storage array is well-defined, so you can reach pointers to the positions of the individual object in the storage array and with std::launder
or by using the pointer returned from the placement-new you can obtain a pointer to the nested object. Even then you will not be able to use pointer arithmetic on pointers to the nested objects, because these do not form an array.
See the paper P0593R5 Implicit creation of objects for low-level object manipulation for further examples of this surprisingly undefined behavior and suggestions to make it defined in the future.
Upvotes: 3