【问题标题】:Once again: strict aliasing rule and char*再次:严格的别名规则和 char*
【发布时间】:2015-03-30 03:49:12
【问题描述】:

我读得越多,我就越困惑。

相关问题的最后一个问题与我的问题最接近,但我对所有关于对象生命周期的词感到困惑,尤其是 - 是否可以只阅读。


直奔主题。如果我错了,请纠正我。

这很好,gcc 不发出警告,我正在尝试“通过char* 读取类型T (uint32_t)”:

uint32_t num = 0x01020304;
char* buff = reinterpret_cast< char* >( &num );

但这很“糟糕”(也会发出警告),我正在尝试“反过来”:

char buff[ 4 ] = { 0x1, 0x2, 0x3, 0x4 };
uint32_t num = *reinterpret_cast< uint32_t* >( buff );

第二个与第一个有何不同,尤其是在我们讨论重新排序指令(用于优化)时?另外,添加const 不会以任何方式改变这种情况。

或者这只是一条直截了当的规则,明确规定:“这可以在一个方向上完成,但不能在另一个方向上完成”? 我在标准中找不到任何相关内容(特别是在 C++11 标准中搜索过)。

这对于 C 和 C++ 是否相同(当我阅读评论时,暗示这两种语言的情况不同)?


我使用union 来“解决”这个问题,这似乎仍然是NOT 100% OK,因为标准不能保证它(其中指出,我只能依赖该值, 最后修改在union)。

所以,读了很多之后,我现在更加困惑了。我猜只有memcpy 是“好”的解决方案?


相关问题:


编辑
实际情况:我有一个第三方库(http://www.fastcrypto.org/),它计算 UMAC,返回值在char[ 4 ]。然后我需要将其转换为uint32_t。而且,顺便说一句,lib 经常使用((UINT32 *)pc-&gt;nonce)[0] = ((UINT32 *)nonce)[0] 之类的东西。无论如何。

另外,我在问什么是对的,什么是错的,以及为什么。不仅是重新排序、优化等(有趣的是-O0 没有警告,只有-O2)。

请注意:我知道大/小端的情况。这里不是这样。我真的很想忽略这里的字节序。 “严格的别名规则”听起来很严肃,比错误的字节序严重得多。我的意思是——比如访问/修改不应该被触及的内存; 任何种 UB。

引用标准(C 和 C++)将不胜感激。我找不到任何关于别名规则或任何相关内容的信息。

【问题讨论】:

  • buff 甚至可能没有适当对齐...
  • “第二个与第一个有何不同”,我假设您的意思是严格考虑寻址和别名,因为该代码是不可移植的。即使对齐不是问题,num 是后者的值也不能保证与前者的初始值 num 相等,除非您在 bigE 平台上。
  • @WhozCraig - 是的,我知道大/小端。是的,我在问它是否便携和可靠,如果不是 - 为什么(我的意思是,我不仅对代码重新排序感兴趣)。
  • 我明白了。这是一个很好的问题,我只是不想让随便的新手看到这一点,并认为这是解决他们 raw-bytes-to-uint32 困境的灵丹妙药。顺便提一下你的问题。由于您对此缺乏研究,任何理智的人都不会投反对票。
  • 规则以“如果程序试图通过非下列类型之一的左值访问对象的存储值,则行为未定义:[...]”开始。在您的第一种情况下,“对象”是uint32_t,您通过char 类型的glvalue 访问它,这是允许的;在第二种情况下,“对象”是charchars 的数组,您通过uint32_t 类型的glvalue 访问它,这不是任何允许的类型。

标签: c++ c reinterpret-cast strict-aliasing


【解决方案1】:

第二个与第一个有何不同,尤其是当我们谈论重新排序指令(用于优化)时?

问题在于编译器使用规则来确定是否允许这样的优化。在第二种情况下,您试图通过不兼容的指针类型读取 char[] 对象,这是未定义的行为;因此,编译器可能会重新排序读取和写入(或执行您可能不期望的任何其他操作)。

但是,“走另一条路”也有例外,即通过字符类型读取某种类型的对象。

或者这只是一条直截了当的规则,明确规定:“这可以在一个方向上完成,但不能在另一个方向上完成”?我在标准中找不到任何相关内容(特别是在 C++11 标准中搜索过)。

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf 第 3.10 章第 10 段。

在 C99 和 C11 中,它是 6.5 第 7 段。对于 C++11,它是 3.10(“左值和右值”)。

C 和 C++ 都允许通过 char * 访问任何对象类型(或者具体来说,对于 C 是字符类型的左值,或者对于 C++ 是 unsigned charchar 类型的左值)。它们不允许通过任意类型访问char 对象。所以是的,该规则是“单向”规则。

我使用 union 来“解决”这个问题,这似乎仍然不是 100% OK,因为它没有得到标准的保证(其中指出,我只能依赖最后在 union 中修改的值) .

尽管标准的措辞非常模糊,但在 C99(及更高版本)中(至少自 C99 TC3 起)很明显(至少从 C99 TC3 开始)意图是允许通过联合进行类型双关。但是,您必须通过联合执行所有访问。也不清楚您是否可以“将联合对象存在”,即联合对象必须先存在,然后才能将其用于类型双关。

返回值在 char[4] 中。然后我需要将其转换为 uint32_t

只需使用memcpy 或手动将字节移动到正确的位置,以防字节顺序出现问题。好的编译器无论如何都可以优化这一点(是的,甚至是对memcpy的调用)。

【讨论】:

  • 两种情况都使用“不兼容的指针类型”。所以,您是说,char* 的例外用于一种方式而不是另一种方式?
【解决方案2】:

我使用 union 来“解决”这个问题,这似乎仍然不是 100% OK,因为它没有得到标准的保证(其中指出,我只能依赖最后在 union 中修改的值) .

Endianess 是造成这种情况的原因。具体来说,01 00 00 00 的字节序列可能意味着 1 或 16,777,216。

做你正在做的事情的正确方法是停止试图欺骗编译器为你进行转换并自己执行转换。

例如,如果 char[4] 是 little-endian(最小字节优先),那么您将执行以下操作。

char[] buff = new char[4];
uint32_t result = 0;
for (int i = 0; i < 4; i++)
    result = (result << 8) + buff[i];

这会手动执行两者之间的转换,并保证在进行数学转换时始终正确。

现在,如果您正在快速进行此转换,那么使用 #if 和您的架构知识来使用枚举自动执行此操作可能是有意义的,正如您提到的那样,但这又远离了可移植解决方案。 (如果你不能确定,你也可以使用这样的东西作为你的后备)

【讨论】:

    猜你喜欢
    • 2014-07-13
    • 2015-10-15
    • 2017-02-25
    • 2018-12-14
    • 1970-01-01
    • 1970-01-01
    • 2016-09-12
    • 1970-01-01
    相关资源
    最近更新 更多