You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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未检测出问题,但这并不代表代码一定正确。


核心问题分析

这段代码存在多处未定义行为,具体如下:

  1. 模板函数绕过构造函数访问"伪对象"

    • Uninitialized和ZeroInitialized仅分配了对齐内存,从未调用T的构造函数,就直接把内存强制转换为T*并解引用。根据C++标准,对象的生命周期从构造函数执行完成才开始,直接将一块内存当作已构造对象访问,本质是在访问"不存在"的对象,属于未定义行为。
    • 对于带默认成员初始化的S,ZeroInitialized把零初始化的内存直接当作S对象,完全绕过了S中c = 1的默认初始化逻辑,违反了对象构造的标准要求。
  2. 读取未初始化内存并拷贝对象

    • Uninitialized里的bytes数组未初始化,解引用转换后的T*并返回时,会触发T的拷贝构造,过程中会读取未初始化的内存内容(比如S的c成员是随机值)。C++标准明确禁止读取未初始化内存,这类操作属于未定义行为。
  3. 重复构造对象且未析构旧对象

    • main中sU和sZ已经通过拷贝初始化创建(尽管是非法创建的对象),之后又用placement new在同一地址上再次构造S对象。标准规定,在已构造的对象地址上执行placement new前,必须先析构原对象,否则属于未定义行为——即便S的析构函数是平凡的,编译器也无法保证操作安全。
  4. 析构非法状态的对象

    • main结束时,sU和sZ会被自动析构,但这两个对象全程处于非法状态(要么是未合法构造的拷贝,要么是被覆盖的旧对象),析构这类对象同样会导致未定义行为。

相关讨论核心总结

  • 拷贝含未初始化成员的结构体:即便结构体类型简单,读取未初始化内存进行拷贝的操作也属于未定义行为,标准不允许此类操作。
  • 绕过构造函数的合法性:C++要求对象必须经过构造才能进入生命周期,任何绕过构造函数直接访问内存区域的行为,都属于访问不存在的对象,必然是未定义行为。

内容的提问来源于stack exchange,提问作者Zaaier

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 20:42:42