C++严格别名规则在数组与动态分配内存中的作用机制探讨
关于C++严格别名与对象生命周期的问题分析
我找不到涵盖严格别名规则所有边缘情况的资料。我理解在C++中访问特定地址上不存在的对象属于未定义行为(UB),比如把uint32_t*转换成float*并访问是非法的,因为该地址没有存活的float对象,而启动这类对象生命周期的唯一方式是使用placement new,简单赋值并不足够。基于此我有以下问题:
// CASE 1 uint64_t arr[8] = {}; //每个元素的动态类型现在是uint64_t? for(int ii = 0; ii < 8; ++ii){ new (arr+ii) double(3.14); //动态类型现在变成double? } arr[0] = 10; //以uint64_t类型解引用,但动态类型是double?我猜这违反严格别名规则? // CASE 2 uint64_t* arr = new uint64_t[8]; //每个元素的动态类型现在是uint64_t? // CASE 3 uint64_t* mal_arr = (uint64_t*)malloc(8*sizeof(uint64_t)); //动态类型现在是void? mal_arr[0] = 10; //如果确实是void,这肯定非法,对吗?
逐个案例分析
CASE 1
- 初始行
uint64_t arr[8] = {};:每个元素的动态类型确实是uint64_t,数组初始化会直接创建8个uint64_t对象并置零。 - 循环中的
new (arr+ii) double(3.14);:placement new会在原有uint64_t对象的地址上构造double对象,这会自动结束原uint64_t对象的生命周期,此时每个数组位置的动态类型变为double。 arr[0] = 10;:这里是通过uint64_t类型的左值去访问动态类型为double的对象,确实违反了严格别名规则,属于未定义行为。严格别名规则明确:除非是字符类型(char/unsigned char/std::byte)这类允许的例外,否则不能用与对象动态类型不匹配的类型去访问它。
CASE 2
uint64_t* arr = new uint64_t[8];:每个元素的动态类型就是uint64_t。new[]会为8个uint64_t对象分配内存并完成初始化(内置类型默认值为0),此时静态类型和动态类型完全匹配,没有问题。
CASE 3
uint64_t* mal_arr = (uint64_t*)malloc(8*sizeof(uint64_t));:malloc只分配原始内存,不会创建任何对象,所以不存在“动态类型为void”的说法——void根本不是对象类型。mal_arr[0] = 10;:这行代码的合法性分版本:- C++20之前:属于未定义行为,因为此时内存上没有
uint64_t对象,直接赋值访问相当于操作一个不存在的对象。必须先用placement new构造对象,比如new(mal_arr) uint64_t(10);才能合法访问。 - C++20及以后:新增了隐式对象创建规则,对于像
uint64_t这样的平凡可析构类型,直接赋值会自动在内存上隐式创建对象,此时这行代码是合法的。
- C++20之前:属于未定义行为,因为此时内存上没有
内容的提问来源于stack exchange,提问作者arc31
相关产品推荐
相关产品推荐

