7

I have a struct X which inherits from struct Base. However, in my current setup, due to alignment, size of X is 24B:

typedef struct {
    double_t a;
    int8_t b;
} Base;

typedef struct {
    Base base;
    int8_t c;
} X;

In order to save the memory, I'd like to unwind the Base struct, so I created struct Y which contains fields from Base (in the same order, always at the beginning of the struct), so the size of the struct is 16B:

typedef struct {
    double_t base_a;
    int8_t base_b;
    int8_t c;
} Y;

Then I'm going to use instance of struct Y in a method which expects a pointer to Base struct:

void print_base(Base* b)
{
  printf("%f %d\n", b->a, b->b);
}
// ...
Y data;
print_base((Base*)&data);

Does the code above violates the strict aliasing rule, and causes undefined behavior?

6
  • Right, fixed the example code Commented Dec 8, 2017 at 8:56
  • Why don't you simply declare Base base; in Y and call the function with print_base(&data.base);? It consumes the same memory as having the members of the struct in the other struct.
    – mch
    Commented Dec 8, 2017 at 8:57
  • 1
    @mch it doesn't. Size of struct X, which uses your approach, takes 24 bytes, while struct Y only 16 Commented Dec 8, 2017 at 8:59
  • It seems to me that the whole type X is irrelevant in this example. You are casting from Y to Base which are independent on X.
    – Marian
    Commented Dec 8, 2017 at 9:04
  • @Marian I wanted to present the right approach, and explain the reason why don't I want to go for it. Commented Dec 8, 2017 at 9:05

1 Answer 1

9

First, Base and Y are not compatible types as defined by the standard 6.2.7, all members must match.

To access an Y through a Base* without creating a strict aliasing violation, Y needs to be "an aggregate type" (it is) that contains a Base type among its members. It does not.

So it is a strict aliasing violation and furthermore, since Y and Base are not compatible, they may have different memory layouts. Which is kind of the whole point, you made them different types for that very reason :)

What you can do in situations like this, is to use unions with struct members that share a common initial sequence, which is a special allowed case. Example of valid code from C11 6.5.2.3:

union {
  struct {
    int alltypes;
  } n;
  struct {
    int type;
    int intnode;
  } ni;
  struct {
    int type;
    double doublenode;
  } nf;
} u;

u.nf.type = 1;
u.nf.doublenode = 3.14;
/* ... */
if (u.n.alltypes == 1)
  if (sin(u.nf.doublenode) == 0.0)
5
  • Thanks. I'm not sure about the memory layout. Doesn't the standard guarantee, that the offset for two first fields (a,b in Base, base_a, base_b in Y) will be the same? Commented Dec 8, 2017 at 9:14
  • 1
    @MarcinKolny This is not about memory layout. Memory layout is the same in both types. This is about informing compiler optimizer. You shall define union {Base b; Y y;}; in your header to inform optimizer that he must reload the value b.a from memory anytime when the value y.base_a changed.
    – Marian
    Commented Dec 8, 2017 at 9:55
  • @Marian thanks, that sounds interesting. Just curious, could you give me point to some articles where I could read more about this? Commented Dec 8, 2017 at 10:26
  • @MarcinKolny For example: blog.regehr.org/archives/1307 . To get more put "strict aliasing" on google.
    – Marian
    Commented Dec 8, 2017 at 11:00
  • It is memory layout and informing the compiler both. The compiler may choose to always allocate a struct on an aligned address, but will not necessarily do so if the struct is replaced with all the members that it contained. There may be a difference in padding bytes.
    – Lundin
    Commented Dec 8, 2017 at 13:03

Not the answer you're looking for? Browse other questions tagged or ask your own question.