C++中分离分配与初始化的实现是否会引发未定义行为?
这段分离声明/分配与初始化的C++代码是否存在未定义行为?
以下是待分析的代码:
#include <new> #include <utility> template<typename T> T Uninitialized() { alignas(T) char bytes[sizeof(T)]; return std::forward<T>(*(T*)(void*)(&bytes)); } template<typename T> T ZeroInitialized() { alignas(T) char bytes[sizeof(T)] = {}; return std::forward<T>(*(T*)(void*)(&bytes)); } struct S { char c = 1; }; int main() { // 尝试延迟(或跳过)栈变量的构造 S sU = Uninitialized<S>(); S sZ = ZeroInitialized<S>(); { // 注:如果不用 placement new,sU 和 sZ 的析构函数会在此块作用域结束,以及 main 结束时各调用一次 new (&sU) S(); new (&sZ) S(); } return 0; }
UBSan未检测出问题,但这并不代表代码一定正确。
核心问题分析
这段代码存在多处未定义行为,具体如下:
模板函数绕过构造函数访问"伪对象"
Uninitialized和ZeroInitialized仅分配了对齐内存,从未调用T的构造函数,就直接把内存强制转换为T*并解引用。根据C++标准,对象的生命周期从构造函数执行完成才开始,直接将一块内存当作已构造对象访问,本质是在访问"不存在"的对象,属于未定义行为。- 对于带默认成员初始化的
S,ZeroInitialized把零初始化的内存直接当作S对象,完全绕过了S中c = 1的默认初始化逻辑,违反了对象构造的标准要求。
读取未初始化内存并拷贝对象
Uninitialized里的bytes数组未初始化,解引用转换后的T*并返回时,会触发T的拷贝构造,过程中会读取未初始化的内存内容(比如S的c成员是随机值)。C++标准明确禁止读取未初始化内存,这类操作属于未定义行为。
重复构造对象且未析构旧对象
main中sU和sZ已经通过拷贝初始化创建(尽管是非法创建的对象),之后又用placement new在同一地址上再次构造S对象。标准规定,在已构造的对象地址上执行placement new前,必须先析构原对象,否则属于未定义行为——即便S的析构函数是平凡的,编译器也无法保证操作安全。
析构非法状态的对象
main结束时,sU和sZ会被自动析构,但这两个对象全程处于非法状态(要么是未合法构造的拷贝,要么是被覆盖的旧对象),析构这类对象同样会导致未定义行为。
相关讨论核心总结
- 拷贝含未初始化成员的结构体:即便结构体类型简单,读取未初始化内存进行拷贝的操作也属于未定义行为,标准不允许此类操作。
- 绕过构造函数的合法性:C++要求对象必须经过构造才能进入生命周期,任何绕过构造函数直接访问内存区域的行为,都属于访问不存在的对象,必然是未定义行为。
内容的提问来源于stack exchange,提问作者Zaaier
相关产品推荐
相关产品推荐

