How does an array of structures with flexible array members behave?
Asked Answered
P

2

5

As the title states I was wondering how arrays of C-structs with a flexible array member behaves. Here is an example:

struct vector {
    size_t length;
    double array[];
};

The Wikipedia article says:

The sizeof operator on such a struct is required to give the offset of the flexible array member.

On my machine this corresponds to 8 bytes (sizeof(size_t)). But what happens, when I do the following:

Obviously the array cannot hold the data of vector v0, since it's only 3*8 bytes = 24 bytes wide. How can I deal with situations like this?

#define LENGTH 10

int main() {
    struct vector arr[3];

    struct vector *v0 = calloc(1, sizeof(*v0) + LENGTH * sizeof(v0->array[0]));
    v0->length = LENGTH;

    size_t i;
    for (i = 0; i < v0->length; i++) {
        v0->array[i] = (double) i;
    }

    struct vector v1;
    struct vector v2;

    arr[0] = *v0;
    arr[1] =  v1;
    arr[2] =  v2;

    for (i = 0; i < arr[0].length; i++) {
        printf("arr[0].array[%2zu] equals %2.0lf.\n", i, arr[0].array[i]);
        printf("    v0->data[%2zu] equals %2.0lf.\n", i, v0->array[i]);
    }
    return 0;
}

For example, when I'm writing a library (header: mylib.h, source: my lib.c) and want to hide the implementation of one specific struct from the user (struct declared in header, defined in source - hidden). Sadly exactly this struct contains a flexible array member. Won't this lead to unexpected behavior, when the user tries to create an array of named structures?

Extra: Read more about the flexible array in the OpenSTD C Spec.
Just search for 'flexible array member'.

EDIT: The latest draft of the C11 Standard, the most up to date freely available reference for the C language is available here: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf

Pulverize answered 20/3, 2016 at 19:36 Comment(6)
"Won't this lead to unexpected behavior, when the user tries to create an array of named structures?" Not if done correctly. If the user is not supposed to know about the flexible array field then the obvious thing to do would be to have APIs that do the allocation for the user.Revolution
@Revolution An API which creates arrays and keeps track of indices and stuff like that? I mean indices won't work with different sized structures... How would that even work? Far from obvious in my opinion.Pulverize
The struct has a length field already. That tells the user precisely what the valid indices are.Revolution
@Revolution Lets say I want to access the fifth element. So I do something like sizeof (struct mystruct) + array[0].length* sizeof (array[0].data[0]) + sizeof (struct mystruct) + array[1].length* sizeof (array[1].data[1]) + sizeof (struct mystruct) + array[2].length* sizeof (array[2].data[2]) + ...? That's kind of nice I guess.. I'm pretty sure that's not the way to handle structures like this properly. It would be way more efficient to avoid the flexible array member, do an extra malloc and use the good old style data pointer-member.Pulverize
With arr[i] = ... you are copying only the first sizeof(size_t) bytes into arr[i].Equip
@M.M That escalated quickly. Lets talk further in chat.Pulverize
C
7

Structures with a flexible array as their last member cannot be used as members of other structures or as array elements. In such constructions, the flexible array cannot be used as it has a size of 0 elements. The C Standard quoted by Jonathan Leffler is explicit, although the language used is quite technical and the paragraphs cannot be found in the Standard by searching for flexible.

The compiler should have issued an error for your array of struct vector.

In your program, you should instead use an array of pointers to struct vectors, each pointing to an object allocated for the appropriate number of elements in the its flexible array.

Here is a modified version:

#include <stdio.h>
#include <stdlib.h>

struct vector {
    size_t length;
    double array[];
};

struct vector *make_vector(size_t n) {
    struct vector *v = malloc(sizeof(*v) + n * sizeof(v->array[0]));
    v->length = n;
    for (size_t i = 0; i < n; i++) {
        v->array[i] = (double)i;
    }
    return v;
}

int main(void) {
    struct vector *arr[3];

    arr[0] = make_vector(10);
    arr[1] = make_vector(5);
    arr[2] = make_vector(20);

    for (size_t n = 0; n < 3; n++) {
        for (size_t i = 0; i < arr[n]->length; i++) {
            printf("arr[%zu]->array[%2zu] equals %2.0lf.\n",
                   n, i, arr[0]->array[i]);
        }
    }
    return 0;
}
Clerc answered 20/3, 2016 at 20:55 Comment(3)
You cannot use them as members of other structures, but, you can however define the array as double **array where each double *array[i] points actually to the flexible array member of struct vector{size_t nitems; double items[]}. By doing pointer arithmetic, you could access the respective nitems counter from each double *array[i] pointer, as I said in this question that I made yesterday: #71993725Pastose
@cesss: you could do that but why use such a complicated and risky approach where an array of pointers to allocated vector structures is simple and straightforward?Clerc
Yes, I agree. There would be a benefit, though: You could use multidimensional indexing syntax (ie: array[i][j]), while with the vector approach you must access items as arr[j]->array[i]Pastose
W
3

You can't have arrays of structures with flexible array members.

The C standard, ISO/IEC 9899:2011, says:

6.7.2.1 Structure and union specifiers

¶3 A structure or union shall not contain a member with incomplete or function type (hence, a structure shall not contain an instance of itself, but may contain a pointer to an instance of itself), except that the last member of a structure with more than one named member may have incomplete array type; such a structure (and any union containing, possibly recursively, a member that is such a structure) shall not be a member of a structure or an element of an array.

Emphasis added — the italic part of that prohibits arrays of structures with flexible array members. You can have arrays of pointers to such structures, though, but each structure will be separately allocated.

¶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. 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; the offset of the array shall remain that of the flexible array member, even if this would differ from that of the replacement array. If this array would have no elements, it behaves as if it had one element but the behavior is undefined if any attempt is made to access that element or to generate a pointer one past it.

This defines a flexible array member.

If you think about it, it makes sense. Pointer arithmetic and arrays rely on all the objects in the array being the same size (hence the equivalence of a[i] == *(a + i), etc), so having an array of objects of varying size would break pointer arithmetic. An array of pointers isn't a problem because the pointers are all the same size, even if the objects pointed at are of different sizes.

If you manage to get a compiler to ignore the violated constraint, then each element of the array will have a zero length flexible array member because the structures will be treated as having the size of the structure without the array member (that's the 'in most situations, the flexible array member is ignored' rule at work). But the compiler should reject an array of a structure type with a flexible array member; such code is violating a constraint (¶3 is in the constraints section; ¶18 is in the semantics section).

Wedgwood answered 20/3, 2016 at 21:13 Comment(6)
Cunning, it carefully avoids using the term "flexible array member" in /3 , so that you do not find it when searchingSpiel
Yes; it hasn't defined 'flexible array member' in ¶3 — that comes in ¶18 — and it doesn't use a forward reference (probably because it is defined in the same section). But the standard is quite explicit that structures with flexible array members cannot be part of an array, even though it doesn't use the term when it says so.Wedgwood
Sorry to follow up on this old question. I am not sure if the standard wording implies that it is OK to have a union containing a struct with FAM as it's member? As in union {struct struct_without_fam a; struct struct_with_fam b }; ?Irreligious
@EugeneSh. — yes, I think that's OK. The first quote says "such a structure (and any union containing, possibly recursively, a member that is such a structure) shall not be a member of a structure or an element of an array". You can't embed that union type into an array or another containing structure, but the union type is allowed. And the rule applies to full copies of the structure or union type, not to pointers to the structure or union type. Note that this means you can't have a discriminated structure type containing a discriminator and the union containing a structure with a FAM.Wedgwood
Thanks. I saw that a compiler does not have any problem with it on any warning level, but weren't sure if it is OK language-wise.. The compiler is also OK with an array of structs with FAM, unless --pedantic is passed, but it is clearly not OKIrreligious
Yes — GCC allows arrays of structures with FAM elements, but all those FAM elements have zero elements, which makes them pointless. Well, maybe the last element of the array could have one or more elements in the FAM, but the rest can't; the space simply isn't allocated for the variable-length arrays that constitute a FAM.Wedgwood

© 2022 - 2024 — McMap. All rights reserved.