flexible array member

C99 allows to define a flexible array member as the last member of a struct, namely an array of undetermined length.

P99_DECLARE_STRUCT(package_head);
struct package_head {
   char name[20];
   size_t len;
   uint64_t data[];
};

Such a struct can then allocated on the heap with a suitable size such that the field data has as much elements as fit in the allocated space from the start of data onward. Usually one would allocate such struct with

package_head *a = malloc(sizeof(package_head) + 10 * sizeof(uint64_t));
package_head *b = malloc(sizeof(*b) + 12 * sizeof(b->data[0]));


This has several disadvantages. First the syntax is clumsy, we have to use a relatively complicated expression that uses two elements of the specification of a or b.

Then this is wasting space. Due to packing of the struct the offset of data “inside” the struct may be less than sizeof(package_head). In most cases the real size of the object that we want to build is

offsetof(package_head, data) + N * sizeof(uint64_t)

so we are wasting

sizeof(package_head) - offsetof(package_head, data)

bytes.

The above formula for the exact size is only valid for larger values of N. We must also make sure that we allocate at least sizeof(package_head) bytes. So the complete formula for looks something like

P99_MAXOF(sizeof(T), offsetof(T, F) + P99_SIZEOF(T, F[0]) * N)

which is probably not something that you want to write on a daily base. A particularity in that expression is P99_SIZEOF(T, F[0]) which stands for the size of the element F[0] inside the struct type T. C doesn’t have the possibility as C++ to refer to a field in a type with something like T::F.

Something similar can be obtained in C99 with the magic formula sizeof((T){ 0 }.F[0]) : define a compound literal (T){ 0 } of type T and take its field F. The sizeof operator actually ensures that this compound literal is never allocated, only the field F is taken for its type and size. This magic works in function scope (the compound literal would be of storage class auto) and in file scope (it would be static).

P99 provides several interfaces to allocate struct with flexible members: P99_FCALLOC, P99_FMALLOC and P99_FREALLOC.

4 thoughts on “flexible array member”

  1. I don’t see how using offsetof() buys you anything. Since structs with flexible array members cannot be stored in arrays, the only padding between the penultimate struct member and the FAM will be that which is needed for alignment of the array.

    Have you actually found a system in which sizeof(package_head) != offsetof(package_head, data)?

    1. I took another look at the Standard, and I think you’re right.

      §6.7.2.1 ¶16 says,

      …the size of the structure is as if the flexible array member were omitted except that it may have more trailing padding than the omission would imply. However, when a . (or ->) operator has a left operand that is (a pointer to) a structure with a flexible array member and the right operand names that member, it behaves as if that member were replaced with the longest array (with the same element type) that would not make the structure larger than the object being accessed

      Consider a system with 64-bit long aligned to 8 bytes, 32-bit size_t aligned to 4 bytes, and char having no alignment requirements. The structure

      struct foo {
      	long tag;
      	size_t len;
      };

      would (since it must be storable in arrays) have a layout something like [tttt_tttt_llll_pppp]
      with a size of 16 bytes. The structure

      struct bar {
      	long tag;
      	size_t len;
      	char data[];
      };

      would then be almost equivalent to

      struct bas {
      	long tag;
      	size_t len;
      	char data[4];
      };

      with struct bar having the layout [tttt_tttt_llll|pppp] and struct bas the layout [tttt_tttt_llll_dddd].

      If the Standard, instead of saying, “it may have more trailing padding than the omission would imply”, would have said, “it may have different trailing padding than the omission would imply”, struct bar could have had the layout [tttt_tttt_llll](d…)i.e., with sizeof(struct bar) == offsetof(struct bar, data). But such is not the case, as ¶17–19 make clear.

      1. Yes exactly. I came up with the following

        struct weird {
          double a;
          short b;
          short c[];
        };
        
        int main(void) {
          printf("sizeof = %zu, offsetof = %zu\n", sizeof(struct weird), offsetof(struct weird, c));
        }
        

        which gives me

        ~/AlGorille 20:09 % /tmp/test-align
        sizeof = 16, offsetof = 10

        with gcc on my 64bit ubuntu system.

      2. Note that the C99 Rationale, §6.7.2.1 (near line #20) is somewhat misleading and says, “sizeof applied to the structure ignores the array but counts any padding before it. This makes the malloc call as simple as possible.”

Comments are closed.