29

Let's say, I have a struct RGB and I want to create struct RGBA, which inherits RGB:

struct RGB {
    unsigned char r;
    unsigned char g;
    unsigned char b;
};

struct RGBA: RGB {
    unsigned char a;
};

Both will be used for reading uncompressed image data:

RGBA *pixel=static_cast<RGBA *>(image->uncompressed_data);

Question: Is this safe, regarding the memory layout of struct RGBA? Does anyone guarantee, that:

  • unsigned char a comes after the RGB struct (not before)
  • There is no padding between struct RGB and the a parameter from struct RGBA?

will #pragma pack help here? It's all about memory layout during inheritance.

Jonas
  • 121,568
  • 97
  • 310
  • 388
Nuclear
  • 1,316
  • 1
  • 11
  • 17
  • 1
    There is no guarantee in the standard that there is no padding between `r` and `g`. There is also no guarantee in the standard about the required alignment of `RGBA`. So even without having to answer the actual question you asked, this is not safe. –  Mar 14 '14 at 12:10
  • Just create an RGBA class with a bool which indicates weather the alpha channel is used or not. – Nils Mar 14 '14 at 12:50
  • You also need to consider endianness. You will save yourself a lot of trouble by just writing pack/unpack functions that do the required bitwise operations. – Neil Kirk Mar 14 '14 at 13:42

5 Answers5

19

No, the layout is not guaranteed. The only guarantees are for standard-layout classes; and one of the conditions of such a class is that it

either has no non-static data members in the most derived class and at most one base class with non-static data members, or has no base classes with non-static data members

In other words, all data members must be in the same class, not in more than one.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
9

There is NO guarantees about the memory layout of derived members and the cast is NOT safe.

As you have inheritance, also there could be padding, this is NOT trivial.

§ 9 Classes

1 A POD struct109 is a class that is both a trivial class and a standard-layout class, and has no non-static data members of type non-POD struct, non-POD union (or array of such types). Similarly, a POD union is a union that is both a trivial class and a standard layout class, and has no non-

Also std::is_pod<RGBA> is not a POD

std::cout << std::boolalpha;
std::cout << std::is_pod<RGBA>::value << '\n';

result is false. see live demo

billz
  • 44,644
  • 9
  • 83
  • 100
2

It's easy to check for padding: Print sizeof(RGB) and sizeof(RGBA). If it's not 3 respective 4 then the structures are padded, and you need to remove it.

As for if the member a comes after b, you can use offsetof to check each members offset. If the offset for a is one larger than the offset of b then a comes directly after b.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • 2
    checking it using compiler XYZ just tells how that compiler implemented it - not whether it is standard conforming behavior. – BЈовић Mar 14 '14 at 12:10
  • @BЈовић As the OP wonders if `#pragma pack` is needed, I'm guessing that the OP have a specific compiler in mind and don't worry about portability. – Some programmer dude Mar 14 '14 at 12:11
  • I have no idea what `#pragma pack` does, and for which compiler it is for. But I think they wanted to know what the standard has to say. – BЈовић Mar 14 '14 at 12:13
  • @BЈовић [`#pragma pack`](http://msdn.microsoft.com/en-us/library/2e70t5y1%28VS.80%29.aspx) forces structs to have certain alignment, overriding whatever alignment the compiler would usually assign. It's supported by MSVC, GCC and Clang. – cf- Mar 14 '14 at 13:21
0

As all previous answers here already stated: it is not guaranteed by the standard. But if you still require such an inheritance of PODs (the derived class is actually no POD anymore), you can at least validate that the current compiler behaves as desired, by using static_assert. If you switch to another compiler that behaves different, then you should at least get compiler errors. Though, this may be bad practice if your code should be easily portable.

I also recommend to use compiler variable attribute __attribute__((packed)) to declare your POD structures and to use <cstdint>. This is valid with e.g. GCC, but might also be different depending on the compiler you use.

#include <cstdint>

struct __attribute__((packed)) RGB {
    uint8_t r;
    uint8_t g;
    uint8_t b;
};

struct __attribute__((packed)) RGBA : RGB {
    uint8_t a;
};

static_assert( sizeof(RGB) == 3u, "Unexpected size" );
static_assert( sizeof(RGBA) == 4u, "Unexpected size" );
wiRe
  • 51
  • 4
0

I maybe wrong but seems to me that you can define:

struct RGBA {
    struct RGB base;
    unsigned char g;
};

and (1) RGBA will be POD, and (2) you can cast both ways:

RRBA x;
RGB* x_rgb = reinterpret_cast<RGB*>(&x);
RGBA* x_rgba = reinterpret_cast<RGBA*>(x_rgb);

I suspect there will be some padding that may be eliminated by attribute((packed)).

uuu777
  • 765
  • 4
  • 21