这真的违反了严格的别名规则吗?

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 已经是&amp;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 的数组,而是intP0137,它形式化了指向的概念,添加了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&lt;int*&gt;(data); 这个表达式,它会违反严格的指针别名。 首先,对data进行数组到指针的转换,结果是指向data的初始元素的指针,这意味着reinterpret_cast&lt;int*&gt;的操作数是指向data的主题的指针。 根据以下规则:

如果在与成员子对象或数组元素 e 关联的存储中创建对象,则创建的对象是 e 的包含对象的子对象,如果:

e 的包含对象的生命周期已经开始但没有结束,并且 新对象的存储正好覆盖与 e 关联的存储位置,并且 新对象与 e 的类型相同(忽略 cv 限定)。

int 类型的对象不满足这些规则。因此,reinterpret_cast&lt;int*&gt; 的操作数不是指向可与 int 类型对象指针互转换的对象的指针。所以,reinterpret_cast&lt;int*&gt; 的结果不是指向 int 类型对象的指针。

程序尝试通过以下类型之一以外的左值访问对象的存储值,行为未定义

对象的动态类型。 [...]

【讨论】:

以上是关于这真的违反了严格的别名规则吗?的主要内容,如果未能解决你的问题,请参考以下文章

从空字节数组转换为结构指针可能会违反严格的别名?

如何投射 so​​ckaddr_storage 并避免违反严格的别名规则

c++ 模板化抽象基类数组,不违反严格别名规则

_Bool类型和严格别名

调用 free() 包装器:取消引用类型双关指针将破坏严格别名规则

严格别名规则“-fstrict-aliasing”和“-fno-strict-aliasing”及类型双关