这真的违反了严格的别名规则吗?
Posted
技术标签:
【中文标题】这真的违反了严格的别名规则吗?【英文标题】:Does this really break strict-aliasing rules? 【发布时间】:2015-01-16 04:43:36 【问题描述】:当我使用 g++ 编译此示例代码时,我收到以下警告:
警告:取消引用类型双关指针将破坏严格别名规则
[-Wstrict-aliasing]
代码:
#include <iostream>
int main()
alignas(int) char data[sizeof(int)];
int *myInt = new (data) int;
*myInt = 34;
std::cout << *reinterpret_cast<int*>(data);
在这种情况下,data
不是为 int 别名,因此将其转换回 int 不会违反严格的别名规则吗?还是我在这里遗漏了什么?
编辑:奇怪,当我这样定义 data
时:
alignas(int) char* data = new char[sizeof(int)];
编译器警告消失。堆栈分配对严格别名有影响吗?它是 char[]
而不是 char*
这一事实是否意味着它实际上不能为任何类型起别名?
【问题讨论】:
@molbdnilo char * 总是可以别名 @ShafikYaghmour 是的,当然。我怎么会忘记? 可能是因为data
已经是&data[0]
的别名? int const * data;
也更接近于 int data[1];
您可能需要考虑为此使用std::aligned_storage
:en.cppreference.com/w/cpp/types/aligned_storage
为什么自 gcc 7.2 以来警告完全消失了?直播(godbolt.org/g/ci5dKj)
【参考方案1】:
警告是绝对有道理的。指向data
的衰减指针确实不指向int
类型的对象,并且转换它不会改变这一点。见[basic.life]/7:
如果,在对象的生命周期结束之后并且在存储之前 被占用的对象被重用或释放,一个新的对象是 在原始对象占用的存储位置创建,a 指向原始对象的指针,引用的引用 到原始对象,或原始对象的名称将 自动引用新对象,并且一旦 新对象已启动,可用于操作新对象,if: (7.1) — [..] (7.2) — 新对象与 原始对象(忽略*** cv 限定符),
新对象不是char
的数组,而是int
。 P0137,它形式化了指向的概念,添加了launder
:
[ 注意:如果不满足这些条件,则指向新对象的指针 可以从表示其地址的指针中获得 通过调用
std::launder
(18.6 [support.dynamic]) 进行存储。 — 结束说明 ]
即你的 sn-p 可以这样纠正:
std::cout << *std::launder(reinterpret_cast<int*>(data));
.. 或者只是从放置 new 的结果中初始化一个新指针,这也消除了警告。
【讨论】:
那么,即使char[]
s 的生命周期已经结束,指针data
衰减到仍然是访问存储在其中的 int 的有效方法?
我明白了,谢谢。我主要担心的是编译器可能会假设data
不能为int
起别名,因此可能会优化*myInt = 34;
对打印语句的影响。
@supercat:memcpy 和 memmove 的相对效率与本次问答有什么关系?
@supercat:更仔细地阅读 Linus。区别是一个 CPU 周期。如果你买不起一个周期,你应该写汇编,而不是 C。
为什么自 gcc 7.2 以来警告完全消失了?直播(godbolt.org/g/ci5dKj)【参考方案2】:
怎么改
std::cout << *reinterpret_cast<int*>(data);
到
int *tmp = reinterpret_cast<int*>(data);
std::cout << *tmp;
?
就我而言,它消除了警告。
【讨论】:
根本问题仍然存在。 @HolyBlackCat 这里没有“潜在问题”。 @curiousguy 我不是母语人士,所以也许“基础”这个词不合适。关键是存在警告,因为 OP 的代码会导致未定义的行为(如另一个答案中所述),正确的做法是更改代码以删除所述 UB,而不是仅仅尝试像这个答案那样使警告静音是的。 @HolyBlackCat 我没有看到任何 UB。 @curiousguy 如果您不同意另一个答案,您能否发布自己的答案,解释为什么 OP 代码的行为是明确定义的以及为什么警告不正确? “没有所谓的“打破严格别名”。这是一个荒谬的想法。”你应该阅读What is the strict aliasing rule?。【参考方案3】:*myInt = 34;
这个表达式格式正确,因为data
为 int 类型的对象提供存储空间,myInt
是指向 int 类型对象的指针。因此,取消引用这样的指针可以访问 int 类型的对象。
对于*reinterpret_cast<int*>(data);
这个表达式,它会违反严格的指针别名。
首先,对data
进行数组到指针的转换,结果是指向data
的初始元素的指针,这意味着reinterpret_cast<int*>
的操作数是指向data
的主题的指针。
根据以下规则:
如果在与成员子对象或数组元素 e 关联的存储中创建对象,则创建的对象是 e 的包含对象的子对象,如果:
e 的包含对象的生命周期已经开始但没有结束,并且 新对象的存储正好覆盖与 e 关联的存储位置,并且 新对象与 e 的类型相同(忽略 cv 限定)。
int 类型的对象不满足这些规则。因此,reinterpret_cast<int*>
的操作数不是指向可与 int 类型对象指针互转换的对象的指针。所以,reinterpret_cast<int*>
的结果不是指向 int 类型对象的指针。
程序尝试通过以下类型之一以外的左值访问对象的存储值,行为未定义:
对象的动态类型。 [...]
【讨论】:
以上是关于这真的违反了严格的别名规则吗?的主要内容,如果未能解决你的问题,请参考以下文章
如何投射 sockaddr_storage 并避免违反严格的别名规则