5

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

Community
  • 1
  • 1
c z
  • 7,726
  • 3
  • 46
  • 59
  • 4
    It is undefined behavior. (And it will be UB even if we use `union`.) – AlexD May 31 '16 at 21:20
  • 3
    "bitshifting workaround" is the correct approach – M.M May 31 '16 at 21:22
  • 2
    Very closely related http://stackoverflow.com/questions/25734477/type-casting-struct-to-integer-c OMG @M.M there's an answer of yours there :O – 101010 May 31 '16 at 21:23
  • Reminder: a compiler is allowed to add padding between and after members. Usually this is performed for *alignment* purposes. – Thomas Matthews May 31 '16 at 21:40
  • To state this clear: What you do is a workaround hack, while bitshifting is the correct way. With a good compiler, this is not even necessarily slower. – too honest for this site May 31 '16 at 21:52
  • 2
    Is this some new C++ feature? You're casting a **scalar** value (integer type) to an **aggregate** (struct type). – Kaz May 31 '16 at 22:35
  • @Kaz it doesn't compile, I suppose OP is just guessing some code without having tried anything – M.M May 31 '16 at 22:43
  • 1
    Fixed the bug in the code (needed to add */&). I guess asking a non-standard question on SO is a bit like ordering milk in a bar :) – c z May 31 '16 at 23:05
  • 1
    There is poor reception in these parts to questions that ask "I know it's not standard, but *how not standard might I be able to get away with*". It is indeed possible to build charts of which compilers act a certain way in which version on which architecture, but the reason standards exist in the first place is to try and make it so we don't have to do that. So if you've reached that point, the thing to do is to show more code of *why* you want to do this thing...to see if there's a better overall approach. – HostileFork says dont trust SE May 31 '16 at 23:07
  • 1
    @cz the problem is posting code that doesn't even compile and acting like the problem is structure padding. `Colour* myColour = reinterpret_cast(&myInteger);` is still a compilation error, you cannot `reinterpret_cast` to struct type and you also cannot assign `Colour` to `Colour *`. – M.M May 31 '16 at 23:15
  • @M.M ""bitshifting workaround" is the correct approach" -- agreed, was just wondering if there were any new features brewing. Other languages like C# support this natively, so always seems a bit hacky. – c z May 31 '16 at 23:45

2 Answers2

3

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.

eerorika
  • 232,697
  • 12
  • 197
  • 326
3

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

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • He said "If my structure is padded, then this won't work, is there any way to guarantee this works?" but his code doesn't compile regardless of whatever struct padding is in place – M.M May 31 '16 at 22:52
  • 1
    @M.M: No, but the rest of it is fairly trivial fixes to the syntax. Yes, you end up violating strict aliasing--that's why I specifically gave the restriction on compilers. – Jerry Coffin May 31 '16 at 23:00
  • If it's so trivial maybe include it in your answer ? – M.M May 31 '16 at 23:01
  • Just tested this in VS. Works for me. – c z May 31 '16 at 23:04
  • 2
    You need to worry about the alignment of `uint32_t` - you want `__attribute__((aligned(4))`. – o11c May 31 '16 at 23:11
  • 1
    @o11c: The alignment requirements are already mentioned, but irrelevant due to the specified restriction to use on an Intel processor (which can read/write either 8- or 32-bit items regardless of alignment). – Jerry Coffin May 31 '16 at 23:44
  • 1
    @JerryCoffin Just because the *machine* can handle it doesn't mean that it's not UB - the compiler could optimize it to something bogus. – o11c Jun 01 '16 at 02:45
  • 1
    @o11c: And nobody said it needed to be defined behavior either. The question specifically says: "I understand this might not be standard, but I'd prefer support for major compilers (Visual Studio and GCC)". Does that sound to you like: "Must be portable to every conforming implementation of C++"? – Jerry Coffin Jun 01 '16 at 03:56