再次:严格的混叠规则和字符*

Once again: strict aliasing rule and char*

本文关键字:规则 字符 再次      更新时间:2023-10-16

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

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


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

这很好,gcc 没有给出警告,我正在尝试"通过 char* 读取类型 Tuint32_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来"解决"这个问题,这似乎仍然不是 100% OK,因为它不能被标准保证(它指出,我只能依赖该值,该值最后在 union 中修改)。

所以,读了很多之后,我现在比较迷茫。我想只有memcpy是"好"的解决方案?


相关问题:

  • 什么是严格的混叠规则?
  • "取消引用类型双关指针将违反严格别名规则"警告
  • 我是否正确理解 C/C++ 严格混叠?
  • 严格的混叠规则和"char *"指针

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

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

请注意:我知道大/小端的情况。这里的情况并非如此。我真的想忽略这里的字节序。"严格的混叠规则"听起来非常严肃,远比错误的字节序严重得多。我的意思是 - 就像访问/修改内存一样,不应该被触及;任何类型的 UB 根本。

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

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

问题在于编译器使用规则来确定是否允许此类优化。在第二种情况下,您尝试通过不兼容的指针类型读取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对象。所以是的,规则是"单向"规则。

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

尽管该标准的措辞非常模棱两可,但在C99(及以后)中,很明显(至少从C99 TC3开始)其意图是通过联合允许类型双关语。但是,您必须通过联合执行所有访问。也不清楚是否可以"强制将联合转换为存在",也就是说,联合对象必须先存在,然后才能将其用于类型双关。

返回值以 char[ 4 ] 为单位。然后我需要将其转换为uint32_t

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

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

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

执行

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

例如,如果char[4]是小端序(最小字节优先),那么您可以执行以下操作。

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

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

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