reinterpret_cast和Structure Alignment

时间:2016-05-31 21:19:33

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

有没有安全的方法可以从整数转换为结构?

举个例子:

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++

2 个答案:

答案 0 :(得分: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<Colour>(myInteger)只是格式不正确,符合编译器的人拒绝直接编译它。

编辑:填充的重要性不是reinterpret_cast<uint32_t*>(&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);

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

答案 1 :(得分:1)

考虑到评论中给出的限制(仅关注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而言,有一个相当简单的规则:操作数和目标类型必须始终是指针或引用(好吧,你可以传递glvalue的名称,但是使用的是一个对这个对象的引用) - 这里的整个想法是获取引用原始对象的东西,但“查看”它就好像它是一个不同的类型,并且要做到这一点,你必须传递一些东西来访问操作数,而不仅仅是它的价值。

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

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上,对齐可能会影响速度)