Is it undefined behavior to use a trivial type without initialization?
void* mem = malloc(sizeof(uint64_t)*100);
void* num_mem = mem sizeof(uint64_t)*31;
//does the lifetime of uint64_t starts here:
uint64_t* mynum = reinterpret_cast<uint64_t*>(num_mem); //?
*mynum = 5; //is it UB?
std::cout << *mynum << std::endl; //is it UB?
free(mem);
I found out that calling of a destructor of trivial/POD/aggregate types is not necessary, but can't find the same for beginning of a lifetime of trivial/POD/aggregate type. Did I have to call new(num_mem) uint64_t;
instead of reinterpret_cast ..
?
Does the behavior changes if it's a POD or aggregate object, without constructors?
CodePudding user response:
All of the following is following the rules of C 20, although the implicit object creation is considered a defect report against earlier versions as well. Before C 17 the meaning of pointer values was very different and so the discussion in the answer and the comments might not apply. All of this is also strictly about what the standard guarantees. Of course compilers may allow more in practice.
Yes, if the type and all of the types of its subobjects are implicit-lifetime types, which is a weaker requirement than being trivial or POD. It does apply to aggregate types, but not necessarily to the subobjects of the aggregate type, in which case these still need to be explicitly new
ed (e.g. consider the member of struct A { std::string s; };
).
You also need to assure that the size and alignment of the memory are suitable. std::malloc
returns memory aligned at least as strict as std::max_align_t
, so it is good for any scalar type, but not for overaligned types.
Also, std::malloc
is one of a few functions that is specifically specified to implicitly create objects and return a pointer to one. It does not generally work for arbitrary memory. For example if you use the memory location as uint64_t
, then the implicitly created object is uint64_t
. Cast afterwards to a different type will cause an aliasing violation.
The details are rather complicated and are easy to get wrong. It is much safer to explicitly create objects with new
(which doesn't require initialization to a value either) and then use the pointer returned by new
to access the object.
Also mem sizeof(uint64_t)*31
is a GNU extension. It is not possible to perform pointer arithmetic on void*
pointers in standard C . You need to cast to the element type first and then perform the arithemtic, assuming that you only store the same types in the memory. Otherwise it gets a bit more complicated.
(Also, you are missing a null pointer check for the return value of malloc
.)