Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 928
c z Avatar asked May 31 '16 21:05

c z


People also ask

What is the point of Reinterpret_cast?

reinterpret_cast is a type of casting operator used in C++. It is used to convert a pointer of some data type into a pointer of another data type, even if the data types before and after conversion are different. It does not check if the pointer type and data pointed by the pointer is same or not.

Is Reinterpret_cast safe?

the result of a pointer-to-pointer reinterpret_cast operation can't safely be used for anything other than being cast back to the original pointer type.

Is reinterpret cast compile-time?

The dynamic cast is the only that needs to be "calculated" in run-time. All other casts are calculated in compile-time. The machine code for a static_cast is a fixed function based on the type you are casting FROM and TO. For reinterpret_cast , the machine code can be resolved in compile-time as well.

Can Reinterpret_cast throw?

No. It is a purely compile-time construct. It is very dangerous, because it lets you get away with very wrong conversions.


2 Answers

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).

like image 55
Jerry Coffin Avatar answered Oct 17 '22 16:10

Jerry Coffin


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.

like image 42
eerorika Avatar answered Oct 17 '22 15:10

eerorika