c z
c z

Reputation: 8967

reinterpret_cast and Structure Alignment

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

Answers (2)

Jerry Coffin
Jerry Coffin

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

eerorika
eerorika

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

Related Questions