【问题标题】:reinterpret_cast and Structure Alignmentreinterpret_cast 和结构对齐
【发布时间】:2016-05-31 21:19:32
【问题描述】:

有什么安全的方法可以将整数转换为结构吗?

举个例子:

struct Colour
{
    uint8_t A;
    uint8_t R;
    uint8_t G;
    uint8_t B;
};

我转换成整数或从整数转换:

uint32_t myInteger
Colour* myColour = reinterpret_cast<Colour*>(&myInteger);
uint32_t* myInteger2 = reinterpret_cast<uint32_t*>(myColour);

如果我的结构被填充了,那么这将不起作用,有什么方法可以保证它起作用吗?

我知道这可能不是标准的,但我更喜欢支持主要编译器(Visual Studio 和 GCC)而不是一些位移解决方法,这里已经回答了:Type casting struct to integer c++

【问题讨论】:

  • 这是未定义的行为。 (即使我们使用union,它也会是UB。)
  • “位移变通办法”是正确的方法
  • 非常相关的stackoverflow.com/questions/25734477/… OMG @M.M 那里有你的答案:O
  • 这是一些新的 C++ 特性吗?您将 scalar 值(整数类型)转换为 aggregate(结构类型)。
  • 修复了代码中的bug(需要添加*/&)。我想问一个关于 SO 的非标准问题有点像在酒吧点牛奶 :)

标签: c++ memory-alignment reinterpret-cast


【解决方案1】:

鉴于 cmets 中给出的限制(只关心 Windows 和 Linux 上的 VC++ 和 gcc),并且假设您愿意进一步将其限制为“在 x86 和可能的 ARM 上运行”,那么您可能会很容易通过通过添加pragma 来确保不会在结构中填充:

#pragma pack(push, 1)
struct Colour
{
    uint8_t A;
    uint8_t R;
    uint8_t G;
    uint8_t B;
};
#pragma pack(pop)

请注意,如果您不关心与 VC++ 的兼容性,您可能希望以不同的方式执行此操作(gcc/g++ 有一个 __attribute__(aligned(1)),否则可能会更受欢迎)。

reinterpret_cast 而言,有一条相当简单的规则:操作数和目标类型必须始终是指针或引用(当然,您可以传递泛左值的名称,但使用的是对那个对象)——这里的整个想法是获取一些引用原始对象的东西,但是“查看”它就好像它是一个不同的类型,并且要做到这一点,你必须传递一些可以访问操作数的东西,不仅仅是它的价值。

如果您想要的结果是一个值(而不是引用或指针),您可以取消引用结果,并将取消引用的结果分配给您的目标。

uint32_t value = *reinterpret_cast<uint32_t *>(&some_color_object);

或:

color c = *reinterpret_cast<Color *>(&some_uint32_t);

鉴于引用的性质,其中一些可能会被隐藏:

color c = reinterpret_cast<Color &>(some_uint32_t);

这里有一小段测试代码,用于进行一些转换并测试/显示结果(使用指针和引用,无论值如何):

#include <iostream>
#include <cassert>

#pragma pack(push, 1)
struct Colour
{
    uint8_t A;
    uint8_t R;
    uint8_t G;
    uint8_t B;

    bool operator==(Colour const &e) const {
        return A == e.A && R == e.R && G == e.G && B == e.B;
    }

    friend std::ostream &operator<<(std::ostream &os, Colour const &c) {
        return os << std::hex << (int)c.A << "\t" << (int)c.R << "\t" << (int)c.G << "\t" << (int)c.B;
    }
};
#pragma pack(pop)

int main() {
    Colour c{ 1,2,3,4 };

    uint32_t x = *reinterpret_cast<uint32_t *>(&c);

    uint32_t y = 0x12345678;

    Colour d = *reinterpret_cast<Colour *>(&y);

    Colour e = reinterpret_cast<Colour &>(y);

    assert(d == e);
    std::cout << d << "\n";
}

请注意上面给出的限制。我已经用 VC++ (2015) 和 g++ (5.3) 对此进行了测试,我猜它可能会在这些编译器的其他版本上工作——但是对于这样的代码来说,没有什么可以保证的.

也完全有可能使用那些编译器,但在不同的 CPU 上,它甚至可以打破。特别是,Colouruint32_t 的对齐要求可能不同,因此在具有对齐要求的 CPU 上,它可能无法工作(即使在 Intel 上,对齐也会影响速度)。

【讨论】:

  • @M.M:不,但其余部分只是对语法的微不足道的修复。是的,你最终违反了严格的别名——这就是我特别限制编译器的原因。
  • 你需要担心uint32_t的对齐 - 你想要__attribute__((aligned(4))
  • @o11c:对齐要求已经提到,但由于在英特尔处理器上使用的指定限制而无关紧要(无论对齐如何,它都可以读/写 8 位或 32 位项目)。
  • @JerryCoffin 仅仅因为 machine 可以处理它并不意味着它不是 UB - 编译器可以将其优化为虚假的东西。
  • @o11c:也没有人说它需要被定义为行为。该问题特别指出:“我知道这可能不是标准的,但我更喜欢支持主要编译器(Visual Studio 和 GCC)”。这听起来像:“必须可移植到每个符合 C++ 的实现”?
【解决方案2】:

我不能说标准在这种情况下保证大小,但是很容易编写一个编译时断言,通过在前提条件不成立的情况下阻止编译来保护您免受大小不匹配引起的 UB:

static_assert(sizeof(Colour) == sizeof(uint32_t),
    "Size of Colour does not match uint32_t. Ask your provider "
    "to port to your platform and tell them that bit shifting "
    "wouldn't have been such a bad idea after all.");

然而,reinterpret_cast&lt;Colour&gt;(myInteger) 只是格式错误,并且符合标准的编译器拒绝直接编译它。

编辑:填充的可能性并不是reinterpret_cast&lt;uint32_t*&gt;(&amp;myColour) 的唯一问题。 uint32_t 可能并且可能比Colour 具有更高的对齐要求。此演员表具有未定义的行为。

有什么安全的方法可以将整数转换为结构吗?

是的:

myColour.A = (myInteger >>  0) & 0xff;
myColour.R = (myInteger >>  8) & 0xff;
myColour.G = (myInteger >> 16) & 0xff;
myColour.B = (myInteger >> 24) & 0xff;

而不是一些位移解决方法。

哦,还有std::memcpy,尽管有对齐差异,但它仍然可以保证工作,尽管与位移不同,它确实需要保持相等大小的断言。

std::memcpy(&myColour, &myInteger, sizeof myColour);

另外,不要忘记,如果您打算将对象的整数表示共享给其他计算机,那么不要忘记转换字节顺序。

【讨论】:

  • 编译器优化仍然会让你陷入困境。
  • @Olaf 如果程序甚至无法编译,编译器优化将如何让我退缩?演员阵容不正确。
  • 我显然不是指格式错误的 sn-p。
  • @Olaf 那么我不太明白你的评论。能否详细解释一下。
  • 我的意思是违反严格的别名规则。该断言不能保证所有可能的用法都对齐。也不是字节序
猜你喜欢
  • 2021-12-17
  • 1970-01-01
  • 2014-07-23
  • 1970-01-01
  • 2012-11-16
  • 2014-02-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多