Are Flexible Array Members Valid in C++

Are flexible array members valid in C++?

C++ was first standardized in 1998, so it predates the addition of flexible array members to C (which was new in C99). There was a corrigendum to C++ in 2003, but that didn't add any relevant new features. The next revision of C++ (C++2b) is still under development, and it seems flexible array members still aren't added to it.

C++ struct array member without given dimension (flexible array member?)

It is one of those things which are different between C and C++. A flexible array member is valid in C but not C++.

That said, many modern compilers compile C as a subset of C++, taking care to care only when you torque up the compiler error diagnostics.

David Tribble spends a moment on it at his Incompatibilities Between ISO C and ISO C++ page, where he specifically addresses this issue:

C++ does not support flexible array members.

(This feature might be provided as an extension by some C++ compilers, but would probably be valid only for POD structure types.)

So yes, this is undefined behavior. The correct way (in both C and C++) to write such a thing is to give it a non-zero dimension:

template <typename T>
struct Vector {
int length;
T ts[1];
};

You have another issue: you must allocate memory for said object. Simply specifying an initializer is not enough. As far as every access to such a thing exists, the compiler only ever thinks it is its minimal size.

This “range hack” is so called because the programmer explicitly uses/abuses C's (and C++'s) ability to violate range bounds to do something tricky.

The consequences of this are many, including inability to store these things in any standard container, or pass them around by value, or do most anything that eschews handling it through a pointer. It has its place, but for the vast majority of use cases C++ has superior options.

Is this array declaration valid in C language?

These need to be at the end of the structure so that the structure's memory can be extended to include them. There's no way to "accordion" out from the middle.

The way you add to this is to "over-allocate" for that structure, like sizeof(struct site) + sizeof(char) * N. The additional memory you allocate becomes available for use as part of that name property.

In general this is a bad plan for simple things like this where what you really need is char* and to copy any string buffer allocations in there either with malloc() and strcpy() or strdup() if you have access to POSIX functions.

So you have two options:

struct site
{
int no_of_pages;
char name[];
};

Where this necessarily means you cannot have any other "extensible" properties, or this:

struct site
{
char* name;
int no_of_pages;
};

Where you can have as many as you want.

A better example for where this technique is useful is if you have a variable length array with metadata, like this:

struct page {
char *name;
int links_count;
struct link links[];
}

Where now you can over-allocate and make use of this links array at the end easily. This could be converted to struct link links* and allocated independently, but that involves two allocations instead of one, so it might have drawbacks. You need to consider use of this technique very carefully.

Is returning a struct with flexible array member allowed?

Yes it is valid C to return such a value, but none of the array elements will be copied. The automatic variable will behave as if it did allocate with an array of length 1 (because array length of 0 is not valid in standard C), but accesses to .data[0] will have UB - the actual size of the object might include the .data[0] and perhaps even more successive elements - or then not.(C11 6.7.2.1p18).

It is not possible to define an automatic variable that would have any content in the flexible array member in standard C (extensions may and do exist)!

An assignment is valid too (and hence returning too), but again the flexible array member will contain indeterminate values (C11 6.7.2.1p25) after assignment.

What is a flexible array member in a struct?

A flexible array member is an array without a specified size. The member has to be the last member of the struct. The actual array size is set when you allocate memory for the struct. Consequently, it only makes sense together with dynamic allocation.

Example:

#define ARRSIZE 10

struct FAM
{
size_t sz;
int arr[]; // Must be last struct member
};

struct FAM* fam = malloc(sizeof(struct FAM) + ARRSIZE * sizeof(int));

Now fam->arr is an array with ARRSIZE elements that you can access ussing fam->arr[index].

Further code like:

struct FAM* famA = malloc(sizeof(struct FAM) + 10 * sizeof(int));
struct FAM* famB = malloc(sizeof(struct FAM) + 1000 * sizeof(int));

will give you two pointers of the same type even though the size of the arrays differ.

So why would I use it?

Look at this code

struct FAM
{
size_t sz;
int arr[];
};

struct P
{
size_t sz;
int* p;
};

int getFAM(struct FAM* f, unsigned long x)
{
return f->arr[x];
}

int getP(struct P* p, unsigned long x)
{
return p->p[x];
}

It's two ways of doing "the same". One using a flexible array member, the other using a pointer.

Compiled with gcc -O2 on https://godbolt.org/ I get

Sample Image

which indicates that the flexible array can save you one indirection and thereby generate faster code.

It can be described like: In case of a flexible array member the array has fixed offset from a struct pointer while in case of a pointer-to-int member the array is located where the pointer value says. Consequently, in the first case the compiler can use that "known fixed offset" directly but in the second case the compiler must read the pointer value first and then read the array data.

Note: This example is for one specific system (aka compiler/cpu type). On other systems the result may differ.

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

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.

Does union support flexible array members?

No, unions do not support flexible array members, only structs. C11 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 addition, zero-length arrays is not valid C, that's a gcc non-standard extension. The reason why you get this to work is because your compiler, gcc, is configured to compile code for the "non-standard GNU language". If you would rather have it compile code for the C programming language, you need to add compiler options -std=c11 -pedantic-errors.



Related Topics



Leave a reply



Submit