该代码现在是合法的,并且追溯自 C++98 起!
@Shafik Yaghmour 的回答是彻底的,并且将代码有效性作为一个未解决的问题 - 回答时就是这种情况。 Shafik 的回答正确地引用了 p0593,在回答时它是一个提案。但从那时起,提案被接受,事情得到了明确。
一些历史
在 C++20 之前的 C++ 规范中没有提到使用 malloc 创建对象的可能性,例如参见 C++17 规范 [intro.object]:
C++ 程序中的构造创建、销毁、引用、访问和操作
对象。对象由定义 (6.1)、新表达式 (8.5.2.4)、
当隐式更改联合的活动成员(12.3)时,或者当临时
对象已创建(7.4、15.2)。
上述措辞并未将malloc 作为创建对象的选项,因此使其成为事实上未定义的行为。
它是then viewed as a problem,这个问题后来由https://wg21.link/P0593R6 解决,并被接受为对自 C++98 (包括 C++98)以来所有 C++ 版本的 DR,然后添加到 C++20 规范中,并带有新的措辞:
[intro.object]
- C++ 程序中的构造创建、销毁、引用、访问和操作对象。对象由定义、new 表达式、通过隐式创建对象的操作创建(见下文)...
...
- 进一步,在指定区域内隐式创建对象后
存储,一些操作被描述为产生一个指向
合适的创建对象。这些操作选择其中之一
隐式创建的对象,其地址是起始地址
的存储区域,并产生一个指针值,指向
该对象,如果该值将导致程序已定义
行为。如果没有这样的指针值会给程序定义
行为,程序的行为是未定义的。如果多个这样
指针值会给程序定义的行为,它是
未指定生成哪个指针值。
C++20 规范中给出的example 是:
#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;
}
至于memcpy 的使用 - @Shafik Yaghmour 已经解决了这个问题,这部分对于普通可复制类型 有效(措辞从 C+ 中的 POD +98 和 C++03 到 普通可复制类型 in C++11 及之后)。
底线:代码有效。
至于生命周期的问题,让我们深入研究有问题的代码:
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);
}
请注意,为了完整起见,可以在释放 buf 之前添加对 *b 的析构函数的调用:
b->~T();
free(buf);
虽然this is not required by the spec.
或者,删除 b 也是一种选择:
delete b;
// instead of:
// free(buf);
但如前所述,代码是有效的。