再一次:严格的别名规则和 char*

Once again: strict aliasing rule and char*

越看越糊涂

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


开门见山。如果我错了请纠正我。

很好,gcc 没有发出警告,我正在尝试 "read type T (uint32_t) via char*":

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

但这是 "bad"(也给出警告)我正在尝试 "the other way around":

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

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

或者这只是一条直截了当的规则,明确规定:"this can be done in the one direction, but not in the other"? 我在标准中找不到任何相关内容(特别是在 C++11 标准中搜索过)。

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


我使用了 union 到 "workaround" 这个,它看起来仍然是 NOT 100% OK,因为它不受标准的保证(声明, 那我只能依赖于 union).

中最后修改的值

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


相关问题:


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

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

请注意:我知道big/little字节序的情况。这里不是这种情况。我真的想忽略这里的字节顺序。 "strict aliasing rules" 听起来很严重,比错误的字节顺序严重得多。我的意思是 - 就像 accessing/modifying 内存,不应该被触及; 任何种UB。

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

How is the second one different from the first one, especially when we're talking about reordering instructions (for optimization)?

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

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

Or this is just a straight rule, which clearly states: "this can be done in the one direction, but not in the other"? I couldn't find anything relevant in the standards (searched for this especially in C++11 standard).

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 对象。所以是的,该规则是“单向”规则。

I used union to "workaround" this, which still appears to be NOT 100% OK, as it's not guaranteed by the standard (which states, that I can only rely on the value, which is last modified in the union).

虽然标准的措辞非常模棱两可,但在 C99(及更高版本)中很清楚(至少自 C99 TC3 以来)intent 是允许类型双关一个工会。但是,您必须通过联合执行所有访问。还不清楚您是否可以“将联合转化为存在”,也就是说,联合对象必须先存在,然后才能将其用于类型双关。

the returned value is in char[ 4 ]. Then I need to convert this to uint32_t

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

I used union to "workaround" this, which still appears to be NOT 100% OK, as it's not guaranteed by the standard (which states, that I can only rely on the value, which is last modified in the union).

Endianess 是造成这种情况的原因。具体来说,字节序列 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 和您的体系结构知识以使用枚举自动执行此操作可能是有意义的,就像您提到的那样,但这再次远离了可移植的解决方案。 (如果你不能确定,你也可以使用类似的东西作为你的后备)