Search code examples
carraysflexible-array-member

Flexible array of flexible arrays in C


Is it possible to use nested flexible arrays (flexible array of flexible arrays) in C?

I tried the following code to test flexible arrays:

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

typedef struct {
    int x;
    int y;
} node;

typedef struct {
    int len;
    node elem[];
} cell;

int cell_size = 3;

int main(void) {
    cell *set = malloc(sizeof *set + cell_size * sizeof set->elem[0]);
    set->len = cell_size;

    for (int j = 0; j < cell_size; j++) {
        set->elem[j].x = j;
        set->elem[j].y = j * 10;
    }

    printf("set size: %d\n", set->len);
    for (int j = 0; j < cell_size; j++) {
        printf("x: %d, ", set->elem[j].x);
        printf("y: %d\n", set->elem[j].y);
    }

    return 0;
}

The output is:

set size: 3
x: 0, y: 0
x: 1, y: 10
x: 2, y: 20

Here everything is fine as expected. Allocated space for set is 28 bytes.

But when I tried to modify this code like this to put flexible array inside other array:

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


typedef struct {
    int x;
    int y;
} node;


typedef struct {
    int len;
    node elem[];
} cell;


typedef struct {
    int len;
    cell group[];
} obj;


int cell_size = 3;
int obj_size = 4;

int main(void) {

    obj *set = malloc(
        sizeof *set + obj_size * sizeof (
        sizeof (cell) + cell_size * sizeof(node)
    ));
    set->len = obj_size;

    for (int i = 0; i < obj_size; i++) {
        set->group[i].len = cell_size;
        for (int j = 0; j < cell_size; j++) {
            set->group[i].elem[j].x = j;
            set->group[i].elem[j].y = j * 10 + i;
        }
    }

    printf("set size: %d\n", set->len);
    for (int i = 0; i < obj_size; i++) {
        printf("group size: %d\n", set->group[i].len);
        for (int j = 0; j < cell_size; j++) {
            printf("x: %d, ", set->group[i].elem[j].x);
            printf("y: %d\n", set->group[i].elem[j].y);
        }
    }

    return 0;
}

Allocated space for set is 20 bytes and the output is wrong:

set size: 4
group size: 3
x: 3, y: 3
x: 3, y: 0
x: 3, y: 1
group size: 3
x: 3, y: 3
x: 0, y: 3
x: 1, y: 13
group size: 3
x: 3, y: 0
x: 3, y: 1
x: 13, y: 2
group size: 3
x: 0, y: 3
x: 1, y: 13
x: 2, y: 23

Even if I set malloc manually to any reasonable value the output is still incorrect. I think this is because the compiler don't know the size of group[] member in the top structure (obj). However I didn't get any compiler errors or warnings (GCC 6.3.0).

And I don't know how to set this size and make compiler process this code correctly.


Solution

  • Structures with flexible array members cannot be used as members to another structure or in an array. You can only have one FAM. Otherwise, if you attempt to declare an array of FAM, each FAM member of the array of struct would point to a memory location between adjacent array members -- giving you the incorrect overwritten results you see.

    For a complete discussion on the C-Standard Sections applicable see: How does an array of structures with flexible array members behave?

    "So, there is no way to make nested flexible arrays working?"

    Answer: NO

    That would violate C11 Standard - 6.7.2.1 Structure and union specifiers(p3)

    "Something similar?"

    Answer: Yes

    All you need to do is make node elem[]; node *elem; and then allocate cell_size * sizeof (node) bytes per set->group[i].elem, e.g.

    typedef struct {
        int len;
        node *elem;
    } cell;
    ...
        obj *set = malloc (sizeof *set + obj_size * sizeof (cell));
        ...
        for (int i = 0; i < obj_size; i++) {
            set->group[i].len = cell_size;
            set->group[i].elem = malloc (cell_size * sizeof (node));
    

    That will allocate cell_size * sizeof (node) for each set->group[i].elem doing what you want, e.g.

    #include <stdlib.h>
    #include <stdio.h>
    
    typedef struct {
        int x;
        int y;
    } node;
    
    typedef struct {
        int len;
        node *elem;
    } cell;
    
    
    typedef struct {
        int len;
        cell group[];
    } obj;
    
    
    int cell_size = 3;
    int obj_size = 4;
    
    int main (void) {
    
        obj *set = malloc (sizeof *set + obj_size * sizeof (cell));
        set->len = obj_size;
    
        for (int i = 0; i < obj_size; i++) {
            set->group[i].len = cell_size;
            set->group[i].elem = malloc (cell_size * sizeof (node));
            for (int j = 0; j < cell_size; j++) {
                set->group[i].elem[j].x = j;
                set->group[i].elem[j].y = j * 10 + i;
            }
        }
    
        printf("set size: %d\n", set->len);
        for (int i = 0; i < obj_size; i++) {
            printf("group size: %d\n", set->group[i].len);
            for (int j = 0; j < cell_size; j++) {
                printf("x: %d, ", set->group[i].elem[j].x);
                printf("y: %d\n", set->group[i].elem[j].y);
            }
            free (set->group[i].elem);
        }
    
        free (set);
    
        return 0;
    }
    

    Example Use/Output

    $ ./bin/fam_array2
    set size: 4
    group size: 3
    x: 0, y: 0
    x: 1, y: 10
    x: 2, y: 20
    group size: 3
    x: 0, y: 1
    x: 1, y: 11
    x: 2, y: 21
    group size: 3
    x: 0, y: 2
    x: 1, y: 12
    x: 2, y: 22
    group size: 3
    x: 0, y: 3
    x: 1, y: 13
    x: 2, y: 23
    

    Memory Use/Error Check

    $ valgrind ./bin/fam_array2
    ==7686== Memcheck, a memory error detector
    ==7686== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
    ==7686== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
    ==7686== Command: ./bin/fam_array2
    ==7686==
    set size: 4
    group size: 3
    x: 0, y: 0
    x: 1, y: 10
    x: 2, y: 20
    group size: 3
    x: 0, y: 1
    x: 1, y: 11
    x: 2, y: 21
    group size: 3
    x: 0, y: 2
    x: 1, y: 12
    x: 2, y: 22
    group size: 3
    x: 0, y: 3
    x: 1, y: 13
    x: 2, y: 23
    ==7686==
    ==7686== HEAP SUMMARY:
    ==7686==     in use at exit: 0 bytes in 0 blocks
    ==7686==   total heap usage: 5 allocs, 5 frees, 168 bytes allocated
    ==7686==
    ==7686== All heap blocks were freed -- no leaks are possible
    ==7686==
    ==7686== For counts of detected and suppressed errors, rerun with: -v
    ==7686== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
    

    Look things over and let me know if you have further questions.