Why is this initialization of a structure with a flexible array member invalid but valid with an fixed size array member?

Viewed 274

The C standard states (emphasize mine):

21 EXAMPLE 2 After the declaration:

struct s { int n; double d[]; };

the structure struct s has a flexible array member d. [...]


22 Following the above declaration:

struct s t1 = { 0 };         // valid
struct s t2 = { 1, { 4.2 }}; // invalid
t1.n = 4;                    // valid
t1.d[0] = 4.2;               // might be undefined behavior

The initialization of t2 is invalid (and violates a constraint) because struct s is treated as if it did not contain member d.

Source: C18, §6.7.2.1/20 + /21

I do not understand the explanation of "because struct s is treated as if it did not contain member d"

If I use the initializer of { 1, { 4.2 }};, the { 4.2 } part is to initialize the flexible array member; To be precise to initialize the flexible array member to be consisted of one element and initialize this element to the value 4.2 and thus stuct s is treated as it has member d or not?

This sentence makes no sense in my eyes.

  • Why does the standard say, that { 4.2 } wouldn't initialize/denote the flexible array member and thus the structure would be treated as if it has no member d?

If I use a fixed size array, this notation works and initializes the member with no complain:

struct foo {
    int x;
    double y[1];
};

int main (void)
{
    struct foo a = { 1, { 2.3 } };
}

Evidence

  • Why is this initialization invalid when the structure has an flexible array member but valid when the structure has an fixed size array member?

Could you elaborate that?


I've read:

Why does static initialization of flexible array member work?

and

How to initialize a structure with flexible array member

and

Flexible array members can lead to undefined behavior?

and others but none of them answers me what this sentence wants to explain and why exactly this this is invalid.


Related:

4 Answers

I do not understand the explanation of "because struct s is treated as if it did not contain member d".

The C standard also says “In most situations, the flexible array member is ignored.” It is unclear why you would not understand what the meaning of this is. If struct s is declared struct s { int n; double d[]; };, then, in most situations, the C implementation behaves as if it were declared struct s { int n; };. Therefore, struct s t2 = { 1, { 4.2 }}; fails because the 4.2 is an initializer for something that, in effect, does not exist.

It is sensible to ask why this is the situation. For the most part, I expect a compiler could support a definition in which the array initializers were counted and used to set the structure size. Certainly compilers do this with array definitions such s int a[] = { 3, 4, 5};. However, that is not the typical use case for flexible array members. Typically, a program receives information about how many elements it will need to manage with the structure, allocates space for the structure with space for those elements included, and then puts a structure in the allocated space. That is, the typical use case for structures with flexible array members is with dynamically allocated space. I expect the C committee saw little need to require compilers to support flexible array members in static or automatic objects, instead of dynamic objects.

I guess this is a language defect. While it might make no sense to initialize a flexible array member, the standard needs to address that issue somewhere. I can't find such normative text anywhere.

The definition of a flexible array member is, C17 6.7.2.1/18:

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.

From this we learn that a flexible array member is an incomplete array type. We do not however learn in what situations the flexible array member is ignored, save for when calculating the size of the struct. "In most situations" isn't helpful and is the defect - this needed to be expanded to an exhaustive list, including the behavior of flexible array members when part of an initializer list. Otherwise one may assume that it behaves just like any other array of incomplete type.

C17 6.2.5/22:

An array type of unknown size is an incomplete type.

And then the rules for initialization say, C17 6.7.9:

The type of the entity to be initialized shall be an array of unknown size or a complete object type that is not a variable length array type.

So far there is no normative text saying that we are not allowed to provide an initializer for a flexible array member - on the contrary. The example in the question (C17 6.7.2.1 example 21) is not normative, since examples aren't normative in ISO standards. The example doesn't mention which constraint that is violated, nor does it mention where it says that the flexible array member must be ignored.

I suppose I'd probably file a DR about this.

You've omitted some important language in the example you quoted - here's the full text:

20 EXAMPLE 2 After the declaration:

struct s { int n; double d[]; };
the structure struct s has a flexible array member d. A typical way to use this is:

int m = /* some value */;
struct s *p = malloc(sizeof (struct s) + sizeof (double [m]));
and assuming that the call to malloc succeeds, the object pointed to by p behaves, for most purposes, as if p had been declared as:

struct { int n; double d[m]; } *p;
(there are circumstances in which this equivalence is broken; in particular, the offsets of member d might not be the same).

IOW, flexible array members only really come into play if you allocate the struct instance dynamically and allocate additional space for the array member.

A flexible array member has no size, so it doesn't contribute to the size of the struct type - that is, the result of sizeof (struct s) evaluates to the size of the type without the array.

IMO it is because sizeof of the initialized this way struct cannot be determined in another compilation unit when the struct is declared as extern.

Related