4

Is it valid C code to have flexible array members inside nested structs? So is my sample code below guarenteed to work as expected with a sane compiler?

#include <stdio.h>
#include <stdlib.h>

struct d {
    char c;
    int ns[];
};

struct c {
    struct d d;
};

struct b {
    struct c c;
};

struct a {
    int n;
    struct b b;
};

int main() {
    const int n = 10;
    struct a *pa = malloc(sizeof(*pa) + n * sizeof(pa->b.c.d.ns[0]));
    pa->n = n;
    pa->b.c.d.c = 1;
    for (int i = 0; i < n; ++i) {
        pa->b.c.d.ns[i] = i;
    }
    for (int i = 0; i < n; ++i) {
        printf("%d\n", pa->b.c.d.ns[i] + pa->b.c.d.c);
    }
    free(pa);
}
  • Yes. You can even let ns just be an int* and dynamically allocate memory. – User Mar 30 '15 at 19:46
  • 1
    In addition to Ulfalizer's answer, it's probably worth mentioning that flexible array members have an indeterminate size. For the `sizeof` operator's sake, flexible array members are not counted in the size of the type themselves, though such members may obviously still affect padding (e.g. you wouldn't start an array of `double` objects at memory address `0xDEADBEEF` due to alignment constraints). –  Mar 30 '15 at 22:15
  • 2
    (continued) Given the lack of size of flexible array members and `struct foo { struct d d; int n; };`, suppose you made `d.ns` an array of 200 items. `n` has a static offset, so `d.ns[0]` might also be `n`. This complication is why nesting a structure possessing a flexible array member is forbidden. –  Mar 30 '15 at 22:15
  • @ChronoKitsune: Good points. I assumed the OP understood that the FAM needed to be at the end, but it's worth mentioning. I updated the answer. – Ulfalizer Mar 31 '15 at 05:02

2 Answers2

8

It's not valid per the standard. I'm not sure how reliable it is in practice.

C11 (ISO/IEC 9899:2011), §6.7.2.1.3 says the following (emphasis mine):

A structure or union shall not contain a member with incomplete or function type (hence, a structure shall not contain an instance of itself, but may contain a pointer to an instance of itself), except that the last member of a structure with more than one named member may have incomplete array type; such a structure (and any union containing, possibly recursively, a member that is such a structure) shall not be a member of a structure or an element of an array.

Later on, §6.7.2.1.18 clarifies that the above is referring to flexible array members (FAMs):

As a special case, the last element of a structure with more than one named member may have an incomplete array type; this is called a flexible array member.

From some quick experimentation, GCC and Clang both add the trailing padding required to align the FAM properly even when the struct is nested, and only warn about structures with FAMs being members of other structures or arrays if -Wpedantic is passed, so take that as a sign that it'll probably work if you will :). It feels a bit hackish though.

Note that it probably wouldn't make sense to have the FAM anywhere but at the end. If you do

struct e {
    struct d d;
    int n;
} e;

, then e.d.ns[0] and e.n are likely to overlap in memory.

Ulfalizer
  • 4,664
  • 1
  • 21
  • 30
0

Try something like this;

struct d {
    char c;
    int ns[];
};

struct a {
    int n;
    int d_fam[];
};

int main() {
    const int n = 10;
    struct a *pa = malloc(offsetof (struct a, d_fam) + offsetof (stuct d, ns) + n * sizeof(int));
    struct d *pd = pa + (uintptr_t) offsetof (struct a, d_fam);
    pa->n = n;
    pd->c = 1;
    for (int i = 0; i < n; ++i) {
        pd->ns[i] = i;
    }
    for (int i = 0; i < n; ++i) {
        printf ("%d\n", pd->ns[i] + pd->c);
    }
    free(pa);
}
bug1
  • 1