为什么使用reinterpret_cast将char*转换为结构体似乎正常工作?

4

有人说使用reinterpret_cast将原始数据(如char*)转换为结构体是不好的。例如,对于以下结构体:

struct A
{
    unsigned int a;
    unsigned int b;
    unsigned char c;
    unsigned int d;
};

sizeof(A) = 16__alignof(A) = 4,正如预期的那样。

假设我这样做:

char *data = new char[sizeof(A) + 1];
A *ptr = reinterpret_cast<A*>(data + 1); // +1 is to ensure it doesn't points to 4-byte aligned data

然后将一些数据复制到ptr

memcpy_s(sh, sizeof(A),
         "\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00", sizeof(A));

然后ptr->a是1,ptr->b是2,ptr->c是3,ptr->d是4。
好的,看起来能用。正是我所期望的。
但是ptr指向的数据并不像应该的A那样对齐到4字节。这可能会在x86或x64平台上引起什么问题?会有性能问题吗?

1
你想知道在某个专有编译器上无效的C++代码是否保证能够正常工作吗? - Neil Kirk
没有看到微软的源代码,就没有人能够回答那个问题。 - Neil Kirk
@NeilKirk 我想知道未对齐的结构体数据可能会引起哪些问题。 - LHLaurini
@NeilKirk 很抱歉,但是这段代码在我这里能够通过gcc、borland、英特尔和msvc编译。 - Zdeněk Jelínek
现在我已经谷歌了一下,发现一些旧的处理器(比如2009年的)可能会稍微慢一些地访问未对齐的内存。 - LHLaurini
1
@Alegnem 我没说它不能编译。 - Neil Kirk
2个回答

3
首先,您的初始化字符串假定底层整数以小端格式存储。但是另一种架构可能使用大端格式,在这种情况下,您的字符串将产生垃圾(一些巨大的数字)。该体系结构的正确字符串应为"\x00\x00\x00\x01\x00\x00\x00\x02\x03\x00\x00\x00\x00\x00\x00\x04"。
然后,当然还有对齐问题。某些体系结构甚至不允许您将数据+1的地址分配给非字符指针,它们会发出内存对齐陷阱。
但即使是允许这样做的体系结构(如x86),也会表现糟糕,每个结构中的整数需要执行两次内存访问。(有关更多信息,请参见此优秀答案:https://dev59.com/G3RC5IYBdhLWcg3wMeBS#381368

最后,我不完全确定这一点,但我认为C和C ++甚至不能保证字符数组中包含的字符是按字节打包的。 (我希望了解更多的人可以澄清这一点。)可以想象,有些体系结构完全无法处理非字对齐数据,在这种体系结构中,每个字符都必须占用整个字。这意味着可以取data + 1的地址是有效的,因为它仍然是对齐的,但是您的初始化字符串将不适合预期的工作,因为其中的前4个字符将覆盖整个结构,从而产生a = 1,b = 0,c = 0和d = 0。


谢谢。 (将是 "\x00\x00\x00\x01\x00\x00\x00\x02\x03\x00\x00\x00\x00\x00\x00\x04",因为第三个变量是 char)。是的,我知道这一点。我想知道的是,未对齐的数据是否会引起问题。 - LHLaurini
谢谢,这就是我想知道的。 - LHLaurini
1
据我所知,读取不对齐的连续数据会导致多读取一两次内存,并且需要进行大量的移位操作。我非常确定你不需要重复读取每个单词。 - Zdeněk Jelínek
好的,那我认为没问题了,因为我的代码不应该在除x86和可能的x64之外的其他处理器架构上运行。 - LHLaurini
1
如果我没记错的话,在C/C++中,“byte”意味着“最小可寻址单元”,并且不一定是8位。 - Cubic
显示剩余4条评论

0
问题是你无法确定这段代码是否会在另一个平台上运行,在 Visual Studio 的下一个版本中运行等情况下能否正常工作。当在另一个处理器上运行时,它可能会导致硬件异常。
以前可以读取任意内存位置,但是现在所有这些程序都会崩溃并显示 "访问冲突" 异常。将来这个程序也可能发生类似的情况。
然而,你可以做的,也是任何自称 "符合 C++ 标准" 的编译器必须正确编译的是:
你可以将指针重新解释为其他类型,再重新转换回原始类型。在读取前后,该类型的值必须保持不变。
我不知道你想做什么,但你也许可以这样做:
分配一个结构体 A
对其进行 reinterpret_cast 转换为字符型
将内存内容保存到文件中
之后再将所有东西恢复:
  • 分配一个struct A
  • reinterpret_cast它为chars
  • 将内容加载到内存中
  • reinterpret_cast它回到struct A

现在,使用x86或x64处理器访问不对齐的数据是否会成为问题?谢谢。 - LHLaurini
(误)对齐只会降低x86/64的性能。真正的问题是不同CPU架构的内存访问模式和具体内存模型可能不支持您的想法。 - Zdeněk Jelínek
那么,我认为这没问题,因为我的代码不应该在除x86和稍后的x64之外的任何其他处理器架构上运行。 - LHLaurini
这正是我在产生疑问时试图做的事情:尝试从文件中读取一个结构体。但是当恢复时,我没有理解“将其重新解释为char类型”。 - LHLaurini
@LHLaurini 不需要从 char * 转换回 A*。(1) 除非 char * 是从对齐的 A 转换而来,否则这是未定义行为。(2) 在这里,它是这样的,但是这是无意义的!将文件加载到 A 的方法 - 如果它是可以平凡复制的,这是必要的 - 是分配 A a 并将文件作为一系列 char 复制到它上面 - 通过读取到 reinterpret_cast<char *>(&a) 或一个单独的缓冲区,然后使用 memcpy 等。然后读取您已经拥有的 a - 没有转换回去的必要。在适当的情况下,副本可以被优化为不对齐的转换。 - underscore_d

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接