Why has the type of an empty array a different size within and outside of a structure?

Viewed 83
#include <stdio.h>    
struct Obj {
    char a;
    uint32_t b;
    uint8_t c;
    uint64_t d[0];
};
struct Obj1 {
    uint64_t d[0];
};

int main() {
    uint64_t a[0];
    printf("%d\n", sizeof(Obj)); // 16
    printf("%d\n", sizeof(a)); // 16
    printf("%d\n", sizeof(Obj1)); // 16
    //cout << sizeof(Obj) << endl; // 16
    //cout << sizeof(a) << endl;   // 0
    //cout << sizeof(Obj1) << endl; // 0
}

As shown above, why the uint64_t variable within the struct will not be stacked right back after the uint8_t, and more peculiar is that the empty array has zero sizes outside the structure.

It's actually an interview question. The explanation goes like this, though still can't understand.

If there is no fourth field, it should be 4+4+4=12, plus the fourth field is 16, the fourth field does not occupy space, but it will tell the compiler to align by 8 bytes

This usage is often used in the kernel, for example, the following can be directly accessed by subscript

Obj o1; uint64_t array[1024]; // In memory, array immediately follows
o1 o1.d[123]; // can access the elements of array

As noted by comments, this may be only applied in C instead of C++. SO I changed the code to C version.

2 Answers

First, your code is undefined behavior. From Arrays p1 emphasis mine:

In a declaration T D where D has the form

  D1 [ constant-expressionopt ] attribute-specifier-seqopt 

and the type of the contained declarator-id in the declaration T D1 is “derived-declarator-type-list T”, the type of the declarator-id in D is “derived-declarator-type-list array of N T”. The constant-expression shall be a converted constant expression of type std​::​size_­t ([expr.const]). Its value N specifies the array bound, i.e., the number of elements in the array; N shall be greater than zero.

The size of an array has to be greater then 0.


As for gcc compiler extension that allows for zero sized arrays in C code and happens to be also supported in C++ code, the gcc documentation states:

Although the size of a zero-length array is zero, an array member of this kind may increase the size of the enclosing type as a result of tail padding.

Which seems to be happening in your code.

This interview question probes a candidates knowledge of alignment and certain semantics in both the C standard and specific implementations.

The char a member has size one (byte) and alignment requirement one (byte).

The uint32_t b member has size four and commonly has an alignment requirement of four bytes. In order to place it on an address that is a multiple of four bytes, the compiler has to include three unused bytes after a and before b, which are calling padding bytes. Up to this point, the structure requires 1+3+4 = 8 bytes.

The uint8_t c member has size one and alignment requirement one. Up to this point, the structure requires 9 bytes.

With uint64_t d[0], the behavior is not defined by the C standard. However, unless the interviewer has specified this is a question about strictly conforming standard C, answering that the behavior is undefined is inadequate, as there is more to C than just the standard. There is also conforming (but not strictly conforming) C and non-standard variants of C. GCC supports a well-known extension in which the last member of a structure may be declared as an array with zero elements, and the interviewer expects the questioner to be aware of this.

When such a structure is used, the program must allocate sufficient space for whatever array elements it wishes to use, by adding such space to the request made with malloc or a similar memory-allocation routine. For example, to allocate space for the base structure plus 13 elements, one might use malloc(sizeof(struct Obj) + 13 * sizeof(uint64_t)).

Commonly, a uint64_t has an alignment requirement of eight bytes. Whatever its alignment requirement is, the compiler will add sufficient unused bytes between members c and d to ensure d has the proper alignment. If it does require eight-byte alignment, then seven bytes must be inserted after c, so the structure size up to the start of d will be 1+3+4+1+7 = 16 bytes.

Related