【问题标题】:Why does using reinterpret_cast to convert from char* to a structure seem to work normally?为什么使用 reinterpret_cast 从 char* 转换为结构似乎可以正常工作?
【发布时间】:2015-05-15 04:43:46
【问题描述】:

人们说信任 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-&gt;a 是 1,ptr-&gt;b 是 2,ptr-&gt;c 是 3,ptr-&gt;d 是 4。
好吧,似乎工作。正是我所期待的。

但是ptr 指向的数据不是像A 应该的4 字节对齐的。这可能会在 x86 或 x64 平台中导致什么问题?性能问题?

【问题讨论】:

  • 您想知道是否可以保证特定编译器上的无效 C++ 代码可以正常工作?
  • 没有看到微软的源代码,没有人能回答这个问题。
  • @NeilKirk 我想知道的是由于未对齐的结构数据可能会出现什么问题。
  • @NeilKirk 很抱歉,这段代码在 gcc、borland 和 intel 以及 msvc 上为我编译。
  • @Alegnem 我没有说它没有编译。

标签: c++ struct reinterpret-cast


【解决方案1】:

一方面,您的初始化字符串假定底层整数以小端格式存储。但是另一种架构可能使用大端,在这种情况下,您的字符串会产生垃圾。 (一些巨大的数字。)该架构的正确字符串是

“\x00\x00\x00\x01\x00\x00\x00\x02\x03\x00\x00\x00\x00\x00\x00\x04”。

当然,还有对齐的问题。

某些架构甚至不允许您将 data + 1 的地址分配给非字符指针,它们会发出 内存对齐陷阱。 p>

但即使是允许这样做的架构(如 x86)也会表现得很糟糕,必须对结构中的每个整数执行两次内存访问。 (有关更多信息,请参阅这个出色的答案: https://stackoverflow.com/a/381368/773113)

最后,我对此并不完全确定,但我认为 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)。是的,我知道这一点。我想知道的是未对齐的数据是否会导致一些问题。
  • 谢谢。这就是我想知道的。
  • 据我所知,读取未对齐的连续数据会给您带来一两次更多的内存读取和大量的移位。我很确定您不需要将每个单词都读两遍。
  • @Alegnem 你是对的,我的措辞很糟糕。我写的是“执行内存访问操作”,而我的意思是“执行内存访问”。当然,是 CPU 做的,而不是程序员。我修好了。
  • IIRC 'byte' 表示 C/C++ 中的“最小可寻址单元”,不一定是 8 位。
【解决方案2】:

问题是你不能确定这段代码是否会在另一个平台上运行,下一个版本的 Visual Studio 等等。在另一个处理器上运行时,可能会导致硬件异常。

曾经有一段时间您可以读取任意内存位置,但现在所有这些程序都会因“访问冲突”异常而崩溃。将来这个程序可能会发生类似的事情。

但是,您可以做什么以及任何自称为“符合 C++ 标准”的编译器必须正确编译的是:
你可以reinterpret_cast一个指向别的东西的指针,然后回到原来的类型。类型的值,在前后读取时,必须保持不变。

我不知道你到底想做什么,但你可能可以逃脱,例如

  • 分配struct A
  • reinterpret_cast@chars
  • 将内存内容保存到文件中

稍后恢复一切:

  • 分配一个struct A
  • reinterpret_cast它到chars
  • 将内容加载到内存中
  • reinterpret_cast 回一个struct A

【讨论】:

  • 谢谢。现在使用 x86 或 x64 处理器,访问未对齐的数据是否有问题?
  • (错误)对齐只会降低性能(在 x86/64 上)。真正的问题是内存访问模式和不同CPU架构的具体内存模型可能不支持你的想法。
  • 好吧,那么我认为这没关系,因为我的代码不应该在 x86 之外的任何其他处理器架构中运行,也许以后的 x64 可能。
  • 当我有这个疑问时,这正是我试图做的:试图从文件中读取结构。但是我在恢复时没有得到“reinterpret_cast it to chars”...
  • @LHLaurini 无需从char * 转换回A*。 (1) 除非char * 是从对齐的A 转换而来的,否则这是UB。 (2) 在这里,它是,但它没有意义!将文件加载到A 的方法 - 如果它可以简单地复制,这是必不可少的 - 是分配A a 并将文件复制为一系列chars - 通过读取reinterpret_cast&lt;char *&gt;(&amp;a) 或单独缓冲区然后memcpy 等(标准保证TC 类型可序列化到char 缓冲区/从char 缓冲区)。然后阅读您已经拥有的a - 没有意义的回溯。在合适的情况下,副本可以优化为未对齐的演员表无论如何
猜你喜欢
  • 2013-01-19
  • 1970-01-01
  • 2018-01-15
  • 2020-08-29
  • 1970-01-01
  • 1970-01-01
  • 2021-12-30
  • 2015-01-21
  • 1970-01-01
相关资源
最近更新 更多