reinterpret_cast 和结构对齐
reinterpret_cast and 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++。
我不能说标准在这种情况下保证了大小,但是很容易编写一个编译时断言来保护您免受大小不匹配导致的 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
有更高的对齐要求。此演员有未定义的行为。
Is there any safe way to cast from an integer to a structure?
是:
myColour.A = (myInteger >> 0) & 0xff;
myColour.R = (myInteger >> 8) & 0xff;
myColour.G = (myInteger >> 16) & 0xff;
myColour.B = (myInteger >> 24) & 0xff;
rather than some bitshifting workaround.
哦,还有 std::memcpy
尽管存在对齐差异,但它仍能保证工作,尽管与位移不同,它确实需要断言大小相等才能保持。
std::memcpy(&myColour, &myInteger, sizeof myColour);
此外,不要忘记,如果您打算将对象的整数表示形式共享给其他计算机,那么请不要忘记转换字节顺序。
考虑到评论中给出的限制(只关心 VC++ 和 Windows 和 Linux 上的 gcc),并假设您愿意进一步将其限制为"running on x86 and possibly 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
而言,有一个相当简单的规则:操作数和目标类型必须始终是指针或引用(好吧,您可以传递泛左值的名称,但是使用什么是对那个对象的引用)——这里的整个想法是得到一些引用原始对象的东西,但是 "views" 它就好像它是一个不同的类型,要做到这一点,你必须传递一些东西可以访问操作数,而不仅仅是它的值。
如果您想要的结果是一个值(而不是引用或指针),您可以解引用该结果,并将该解引用的结果分配给您的目标。
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);
这里有一小段测试代码,用于进行一些转换和 test/display 结果(同时使用指针和引用,无论其价值如何):
#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 上。特别是,Colour
和 uint32_t
的对齐要求可能不同,因此在具有对齐要求的 CPU 上,它可能不起作用(甚至在 Intel 上,对齐可能会影响速度)。
有什么安全的方法可以将整数转换为结构吗?
举个例子:
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++。
我不能说标准在这种情况下保证了大小,但是很容易编写一个编译时断言来保护您免受大小不匹配导致的 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
有更高的对齐要求。此演员有未定义的行为。
Is there any safe way to cast from an integer to a structure?
是:
myColour.A = (myInteger >> 0) & 0xff;
myColour.R = (myInteger >> 8) & 0xff;
myColour.G = (myInteger >> 16) & 0xff;
myColour.B = (myInteger >> 24) & 0xff;
rather than some bitshifting workaround.
哦,还有 std::memcpy
尽管存在对齐差异,但它仍能保证工作,尽管与位移不同,它确实需要断言大小相等才能保持。
std::memcpy(&myColour, &myInteger, sizeof myColour);
此外,不要忘记,如果您打算将对象的整数表示形式共享给其他计算机,那么请不要忘记转换字节顺序。
考虑到评论中给出的限制(只关心 VC++ 和 Windows 和 Linux 上的 gcc),并假设您愿意进一步将其限制为"running on x86 and possibly 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
而言,有一个相当简单的规则:操作数和目标类型必须始终是指针或引用(好吧,您可以传递泛左值的名称,但是使用什么是对那个对象的引用)——这里的整个想法是得到一些引用原始对象的东西,但是 "views" 它就好像它是一个不同的类型,要做到这一点,你必须传递一些东西可以访问操作数,而不仅仅是它的值。
如果您想要的结果是一个值(而不是引用或指针),您可以解引用该结果,并将该解引用的结果分配给您的目标。
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);
这里有一小段测试代码,用于进行一些转换和 test/display 结果(同时使用指针和引用,无论其价值如何):
#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 上。特别是,Colour
和 uint32_t
的对齐要求可能不同,因此在具有对齐要求的 CPU 上,它可能不起作用(甚至在 Intel 上,对齐可能会影响速度)。