Reputation: 7369
struct T{
T(){}
~T(){}
};
int main(){
auto ptr = (T*)malloc(sizeof(T)*10); // #1
ptr++;
}
Since T
is not an implicit-lifetime class, the operation malloc
can never implicitly create an object of class type T
. In this example, we intend to make ptr
points to the initial element of the array object with 10 elements of type T
. And based on this assumption, ptr++
would have a valid behavior. As per [intro.object] p11
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.
Since an array of any type is an implicit-lifetime type, malloc(sizeof(T)*10)
can implicitly create an array object with 10 elements and start the lifetime of that array object. Since the initial element and the containing array are not pointer-interconvertible, ptr
can never point to the initial element of that array. Instead, the operation can only produce the pointer value of the array object. With this assumption, we should divide #1
into several steps to make the program well-formed?
// produce the pointer value points to array object of 10 T
auto arrPtr = (T(*)[10])malloc(sizeof(T)*10);
// decay the array
auto ptr =*arrPtr; // use array-to-pointer conversion
ptr++;
we should first get the array pointer and use that pointer value to acquire the initial element pointer value? Are these processes necessary in contributing to making the program well-formed?
Upvotes: 18
Views: 405
Reputation: 12342
I think the first example is perfectly valid but rather pointless. The array returned by malloc has not initialized any of the objects. While I think you can form a pointer to each object and iterate over the array you must not dereference the pointer at any time.
In my opinion the only thing that is missing here is the use of either placement new or construct_at like this:
struct T{
T(){}
~T(){}
};
int main(){
constexpr int SIZE = 10;
auto ptr = (T*)malloc(sizeof(T)*SIZE); // #1
for (auto p = ptr; p < &ptr[SIZE]; ++p) {
std::construct_at(p);
}
ptr++;
}
The objects in the array can only be constructed using pointer arithmetic or array indexing ptr[i]
, which is equivalent to *(ptr + i), so basically the same as p++
. If your initial example is UB then construct_at calls above would be UB too. Only way around that would be to treat the return of malloc as byte array and calling construct_at every sizeof(T) bytes. After construction the array could then be cast to the right type, I hope. But that would be silly and I hope nobody finds a reason why that should be neccessary.
Overall if your example where illegal then how would you ever manage to implement new() itself?
Upvotes: 1