C++ strict aliasing规则疑问:指针别名合法性、对齐要求及违例判定
以下回答均针对C++标准规范:
1. char指针转其他类型指针的合法性
首先需要明确:C++的strict aliasing规则仅允许char*、unsigned char*、std::byte*三类指针别名任意类型的内存,反过来不允许任意类型指针别名char类型的内存,除非对应内存区域已经存在该类型的有效对象。
你给出的示例:
char myChars[4]; int* pInt = (int*)myChars; // 该强转本身语法合法,但后续访问需满足前提
- 如果你只是将该char数组作为原始缓冲区,后续会通过placement new在对应地址创建int对象,那这个写法完全合法,也是工程中常用的缓冲区使用方式:
new (pInt) int(10); // 在缓冲区上构造int对象 *pInt = 20; // 此时访问符合规则 - 如果你没有在缓冲区上构造int对象,直接通过
*pInt读写内存,就属于strict aliasing违规,是未定义行为。
你提到的int* myNewInt = (int*)malloc(sizeof(int));合法的逻辑和上述场景一致:malloc返回的是未构造对象的原始存储,你可以在上面构造int对象后访问,不属于违规。
你标注的float* pFloat = (float*) pInt;确实属于strict aliasing违规,int和float是不兼容的类型,没有互相别名的权限。
2. 对齐要求相关问题
- 任何指针类型的转换,都要求目标地址符合目标类型的对齐要求,否则指针本身就是无效值,访问会触发未定义行为。由于char类型的对齐要求是1,所以任意地址转char都不会有对齐问题,但char转其他类型必须满足目标类型的对齐要求。
- 栈上分配的普通char数组默认仅保证1字节对齐,确实无法满足int、float等类型的对齐要求,该说法正确。如果需要栈上的对齐缓冲区,可以用
alignas关键字声明:alignas(int) char myChars[4]; // 该数组对齐要求和int一致 - new和malloc默认返回的地址符合所有基础类型的最大对齐要求,因此默认不会出现对齐问题(如果是自定义的过对齐类型,需要用专门的对齐分配接口)。
3. strict aliasing规则的约束阶段
strict aliasing规则仅约束内存的访问行为,不约束指针的赋值、转换行为:只要你不通过不兼容类型的指针访问内存,单纯的指针类型转换哪怕完全不匹配,也不会触发未定义行为。
针对你给出的示例:
MyStruct myStructObj; float* pFloat = &myStructObj.myInt; // 单纯赋值本身不违规,仅在访问*pFloat时违规 pFloat += 1;
只要你没有在偏移前访问*pFloat,之前的错误赋值不会导致程序存在UB。如果偏移后的地址确实指向myStructObj.myFloat且对齐正确,此时访问*pFloat是合法的。但该写法非常不推荐:结构体成员之间可能存在内存填充,你无法保证pFloat +=1刚好偏移到myFloat的地址。
你给出的第二种写法:
float* pFloat = (float*)(((char*)&myStructObj.myInt) + sizeof(int));
也不保证合法,核心问题还是结构体可能存在内存填充,&myStructObj.myInt + sizeof(int)不一定等于&myStructObj.myFloat的地址。如果一定要通过指针偏移获取结构体成员地址,应该使用标准offsetof宏:
float* pFloat = (float*)((char*)&myStructObj + offsetof(MyStruct, myFloat));
该写法是符合规则的:char*可以别名结构体的内存,偏移到对应成员的地址后,只要该地址确实存在float类型的有效对象,访问就不会触发strict aliasing违规。
内容的提问来源于stack exchange,提问作者Zebrafish

