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.