2016-05-31 67 views
3

是否有任何安全的方法从一个整数转换为结构?reinterpret_cast和Structure Alignment

举个例子:

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

+4

这是未定义的行为。 (即使我们使用'union'也是UB。) – AlexD

+3

“bitshifting workaround”是正确的方法 –

+2

非常相关http://stackoverflow.com/questions/25734477/type-casting-struct-to-integer- c OMG @MM那里有你的答案:O – 101010

回答

0

考虑到评论中给出的限制(只关心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); 

鉴于引用的性质,有可能为一些本应隐藏:

下面是测试代码的快速位做一些转换并测试/显示结果(使用指针和参考,无论值得多少):

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

+3

Downvoter:关心添加评论,为什么你认为这没有帮助?哦,等等,计时器仍然说“刚才回答”时,downvote出现了。在投票前你甚至没有看到答案! –

+0

我没有投票,但你还没有回答这个问题(“是否有任何安全的方法从一个整数投射到结构?”)。你刚刚发布了一个结构。 –

+1

@ M.M:他特别询问了填充。这涉及到这一点。 –

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; 

,而不是一些bitshifting解决方法。

哦,还是有std::memcpy这是保证,尽管定位不同的工作,虽然不像位移位,它确实需要大小相等的断言举行。

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

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

+0

仍然编译器优化可以踢你在b(ack)。 – Olaf

+0

@Olaf如果程序甚至没有编译,编译器优化会如何反弹?演员阵容不健全。 – user2079303

+0

我的意思并不是指形成不良的片段。 – Olaf