Does the order of members in a struct matter?

I have found a peculiar behaviour in C. Consider the below code:

 struct s {
int a;
};


struct z {
int a;
struct s b[];
};


int main(void) {
return 0;
}

It compiles just fine. Then change the order of the members of struct z like so

struct z {
struct s b[];
int a;
};

And all of a sudden we get the compilation error field has incomplete type 'struct s []'.

Why is that?

18025 次浏览

The order of member usually matters (i.e. some padding might be inserted between fields) but in your specific case you're using a flexible member array, this is standardized in C99 - 6.7.2.1.16

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. In most situations, the flexible array member is ignored. In particular, 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.

Your struct s b[]; member is meant to be used to access dynamic heap allocations for multiple struct s elements.

The order of fields in a struct does matter - the compiler is not allowed to reorder fields, so the size of the struct may change as the result of adding some padding.

In this case, however, you are defining a so-called flexible member, an array the size of which you can change. The rules for flexible members are that

  • There may never be more than one such member,
  • If present, the flexible member must be the last one in the struct, and
  • The struct must have at least one member in addition to the flexible one.

Take a look at this Q&A for a small illustration on using flexible structure members.

The compiler can't calculate how much memory struct s b[]; will consume. This means that if the structure has any fields after it, the compiler can't figure out the where those fields are.

It used to be (in old versions of C) that (e.g.) struct s b[]; wasn't allowed as a member of a structure. This made efficient memory management annoying. For a simple example, imagine you've got a structure containing a "name" string (that could be just a few characters or a lot of them). You could use a fixed size array that's big enough for the largest name (which wastes space), or use a pointer and allocate 2 pieces of memory (one for the structure and one for the variable length name string). Alternatively, you could use a pointer and make it point to extra space past the end of the structure, which ends up something like this:

    length = strlen(my_string);
foo = malloc(sizeof(MYSTRUCTURE) + length + 1);
foo->name = (void *)foo + sizeof(MYSTRUCTURE);   // Set pointer to extra bytes past end of structure
memcpy(foo->name, my_string, length + 1);

This was the most efficient option; but it's also ugly and error prone.

To work around that, compilers added non-standard extensions to allow "unknown size arrays" at the end of the structure. This made it a little easier for programmers and made it a little more efficient (as there's no need for the additional pointer member). This ended up being adopted by the C standard (maybe in C99 - I don't remember).

The title of your question is "Does the order of members in a struct matter?".

The obvious problem in your code relates to the fact that your struct contains a flexible member.

So here is an additional issue related to the general question of the order of members in a struct:


Take the following two structures for example:

struct s1
{
int a;
short b;
char c;
};


struct s2
{
char c;
short b;
int a;
};

Most compiler will add padding in order to align each member to an address divisible by its size.

So struct s2 may ultimately compile into:

struct s2
{
char c;
char pad1;
short b;
short pad2;
short pad3;
int a;
};

This will eventually result in different sizes for instances of types struct s1 and struct s2.

The order does matter IN THIS CASE. Your struct z contains an array composed of structs s. However, this array does not have a size associated with it so the compiler does not know how to allocate the appropriate stack space, since there is another field of the struct (int a) afterward. An example of how it WOULD work:

struct s {
int a;
}


struct z {
struct s b[10];
int a;
}


int main(void) {
return 0;
}

If you really need the array to change size, it is best if you allocate the whole structure on the heap with the array as a pointer to struct s and then dynamically reallocate it to accommodate the changing array size. Look up malloc (3), realloc 3), calloc (3), and free (3).