Reputation: 8967
Is there any safe way to cast from an integer to a structure?
As an example:
struct Colour
{
uint8_t A;
uint8_t R;
uint8_t G;
uint8_t B;
};
And I cast to or from an integer:
uint32_t myInteger
Colour* myColour = reinterpret_cast<Colour*>(&myInteger);
uint32_t* myInteger2 = reinterpret_cast<uint32_t*>(myColour);
If my structure is padded, then this won't work, is there any way to guarantee this works?
I understand this might not be standard, but I'd prefer support for major compilers (Visual Studio and GCC) rather than some bitshifting workaround, which has already been answered here: Type casting struct to integer c++.
Upvotes: 5
Views: 4981
Reputation: 490108
Given the restrictions given in the comments (only care about VC++ and gcc on Windows and Linux), and assuming you're willing to further restrict that to "running on x86 and possibly ARM", you can probably get by pretty easily by adding a pragma
to ensure against padding in the structure:
#pragma pack(push, 1)
struct Colour
{
uint8_t A;
uint8_t R;
uint8_t G;
uint8_t B;
};
#pragma pack(pop)
Note that if you didn't care about compatibility with VC++, you might want to do this differently (gcc/g++ has an __attribute__(aligned(1))
that might otherwise be preferred).
As far as reinterpret_cast
goes, there's a fairly simple rule: the operand and target type must always be either a pointer or a reference (well, you can pass the name of a glvalue, but what's used is a reference to that object)--the whole idea here is to get something that refers to the original object, but "views" it as if it were a different type, and to do that, you have to pass something that gives access to the operand, not just its value.
If the result you want is a value (rather than a reference or pointer) you can dereference the result, and assign the result of that dereference to your target.
uint32_t value = *reinterpret_cast<uint32_t *>(&some_color_object);
or:
color c = *reinterpret_cast<Color *>(&some_uint32_t);
Given the nature of references, it's possible for some of this to be hidden:
color c = reinterpret_cast<Color &>(some_uint32_t);
Here's a quick bit of test code to do some conversions and test/display the results (using both pointers and references, for whatever that may be worth):
#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";
}
Do note the restrictions given above though. I've tested this with both VC++ (2015) and g++ (5.3), and I'd guess it'll probably work on other versions of those compilers--but there's not much of anything in the way of guarantees with code like this.
It's also entirely possible that it could break even with those compilers, but on a different CPU. In particular, the alignment requirements for your Colour
and for a uint32_t
could be different, so on a CPU that has alignment requirements, it might not work (and even on an Intel, alignment could affect speed).
Upvotes: 3
Reputation: 238321
I can't say that standard guarantees the size in this case, but it's easy to write a compile time assert that would protect you from UB caused by mismatched sizes, by preventing compilation in case the precondition doesn't hold:
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.");
However, reinterpret_cast<Colour>(myInteger)
is simply ill-formed and conforming compilers refuse to compile it outright.
Edit: Pontential of having padding is not the only problem with reinterpret_cast<uint32_t*>(&myColour)
. uint32_t
may and likely has higher alignment requirement than Colour
. This cast has undefined behaviour.
Is there any safe way to cast from an integer to a structure?
Yes:
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.
Oh, well there's still std::memcpy
which is guaranteed to work despite alignment differences, although unlike bit shifting, it does require the assertion of equal sizes to hold.
std::memcpy(&myColour, &myInteger, sizeof myColour);
Also, don't forget that if you intend to share the integer representation of the object to other computers, then don't forget to convert endianness.
Upvotes: 3