Flexible Arrays

A Tutorial by John Findlay

Flexible array members were introduced with C99, the C standard, compiled by the ISO standards committee.

"This International Standard specifies the form and establishes the interpretation of programs expressed in the programming language C. Its purpose is to promote portability, reliability, maintainability, and efficient execution of C language programs on a variety of computing systems."

A normal array has members that are declared with x number of items like so

int myarray[100];

this gives one hundred memory slots to store integers. This is fine for many purposes but everyone at some time needs a way to use an expandable array. Since the standards committee produced the C99 standard C compilers have gradually been adding the new features and many now support this flexible array feature.

"The last element of a structure with more than one named member may have an incomplete array type." 

Here is an example of a structure with the last member declared as a flexible array that we will use in this tutorial. Only the last member of the structure can have a flexible array - for reasons that will become obvious later.

typedef struct _THESTRUCT
    int    maxnum;
    int    count;
    char * str[];
}THESTRUCT;

Notice the last member, char * str[] in which no size of the array has been declared. This last member can be of any type including another structure so it is (excuse the pun) very flexible. 

A flexible array member needs to be given some memory at run time in which to store its elements - normally one would use malloc to allocate the memory for the structure. 

THESTRUCT * ts = malloc(sizeof(THESTRUCT) + (sizeof(char**) * num_elements));

We now have a memory block sizeof(THESTRUCT) + num_elements of sizeof(char**) in size. 

The size of THESTRUCT is actually only sizeof(int) * 2, this is because char * str[] has no size as a flexible array is an incomplete type that the compiler cannot compute. Do not size sizeof with a structure that has a flexible array member and expect the size to include the size of the flexible array.
 
Before C99 there was a way of achieving the same thing before these flexible arrays were added  but the declaration of a flexible array could be seen as a cleaner definition.

We could have the structure like so

typedef struct _THESTRUCT
    int maxnum;
    int count;
    char ** str;
}THESTRUCT;
Where char ** str achieves the same effect that a flexible array does.

 

After the new structure has been allocated one can access the flexible array members. 

THESTRUCT * ts = malloc(sizeof(THESTRUCT) + (sizeof(char**) * 10));

The above allocation gives us our structure that includes ten slots to store pointers to arrays of char.

 

ts->str[0] = malloc(size);
ts->str[1] = malloc(size);
ts->str[2] = malloc(size);
ts->str[3] = malloc(size);

etc.

So for demonstration purposes we will use this structure with its flexible array to store C strings in a way that allows for the expanding array. The example listings accompanying this tutorial are called flex1.c and flex2.c. 

The function main() starts by creating the structure with a call to init_thestruct().

typedef struct _THESTRUCT
    int maxnum;     // how many pointers are available
    int count;      // how many have we used
    char * str[];   // pointers to pointers of char (*str[])
}THESTRUCT;

int main (void)
{
    int i;
    char s[200];

    // Initialise the structure that will contain our strings
    THESTRUCT * tmp, * ts = init_thestruct(2);
    if(!ts){
        printf("No memory today!\n");
        return 0;
    }

    for(i = 0; i<10; i++){
        sprintf(s, "String %d", i);

        // Add each string - if more pointers are required
        // add_string calls realloc_struct()
        if(NULL != (tmp = add_string(ts, s))){

            // The struct memory may have changed if it was realloc-ed
            // so we need to re-assign our pointer to struct, 'ts'
            ts = tmp;

        }else{
        
            // oops, no memory!
            break;
        }
    }

    for(i = 0; i<ts->count; i++){
        printf("%s\n", ts->str[i]);
    }
    free_struct(ts);

    return 0;
}

For this demo we will start the array having only two array elements. Here is the function init_thestruct().

THESTRUCT * init_thestruct( int num )
{
    // Allocate memory for the structure which includes
    // num pointers to char *[]
    THESTRUCT * ts = malloc(sizeof(THESTRUCT) + sizeof(char**) * num );
    if(ts){

        // Set the number of possible pointers
        ts->maxnum = num;

        // no strings as yet
        ts->count = 0;
        return ts;

    }else{
        return NULL;
    }
}

If the memory allocation is successful we set ts->maxnum so we have a record of how many pointers to C strings are possible, also set ts->count to zero as we have not added any strings so far. If malloc was unsuccessful we return NULL.

In main() the for loop attempts to add ten strings with calls to add_string() passing the pointer to the structure and the pointer to the string to add.

The function add_string first allocates memory for the new string, if this is successful it then checks that the count of added strings has not reached the maximum, ts->maxnum. If the maximum has been reached a call to realloc_struct() will re-size the structure to accommodate the extra string pointers. If the maximum has not been reached, the pointer to newly allocated memory will be added and then the string will be copied to this new memory, the count will be incremented and the possibly new address of the structure is returned to the caller.

ts->str[ts->count] = tmpc;
strcpy(ts->str[ts->count], str);
ts->count++;
return ts;
When using run time library's realloc() the original address may not be returned so we have to reassign our pointer to this possibly new address.
 
Here is the add_string function.

THESTRUCT * add_string( THESTRUCT * ts, char * str )
{
    THESTRUCT * tmp;

    // Allocate enough memory for the string + 1
    char * tmpc = malloc(strlen(str)+1);
    if(!tmpc){
        return NULL; // no mem
    }else{

        // Check, are there enough pointers,
        // if not go make some more
        if(ts->count == ts->maxnum){
            tmp = realloc_struct(ts);

            if(tmp == NULL)
                return NULL;
        else
            ts = tmp;
        }

        // Make the next pointer contain the new string memory pointer
        ts->str[ts->count] = tmpc;

        // copy the string to our newly allocated memory
        strcpy(ts->str[ts->count], str);

        // increment counter for the next time
        ts->count++;
        return ts;
    }
}

The pivotal function realloc_struct() is passed the pointer to the structure where it is re-sized by realloc with the new size that is calculated by increasing the maxnum variable in the temporary variable num.

int num = ts->maxnum;
num *= 2;
THESTRUCT * tmp = realloc(ts, sizeof(THESTRUCT) + sizeof(char**) * num );

If this call to realloc is successful the variable ts->maxnum will be set at the new size, and the possibly new address of the structure will be returned to the caller.

The realloc_struct() function.

THESTRUCT * realloc_struct( THESTRUCT * ts )
{
    int num = ts->maxnum;

    // Double the num of pointers that the structure will hold
    num *= 2;

    // Allocate the new memory for the structure and pointers
    THESTRUCT * tmp = realloc(ts, sizeof(THESTRUCT) + sizeof(char**) * num );
    if(!tmp){
        return NULL;
    }else{

        // Set new maxnum pointers and new struct pointer
        ts = tmp;
        ts->maxnum = num;

        return ts;
    }
}

One thing remains, the cleaning / free-ing of the memory allocated when using this little demo. The function free_struct() does this. First, all memory allocated for the strings is free-ed then the structure itself.

void free_struct( THESTRUCT * ts )
{
    // free all memory accoiated with our structure
    if(ts){
        for(int i = 0; i<ts->count; i++)
            free(ts->str[i]);
        free(ts);
    }
}

The accompanying sources include two listings, flex1.c uses the flexible array as used in the above, char *str[], and flex2.c uses the old style flexible array char ** str.

My preference is to use the old style flexible arrays because then one does not need to re-allocate the struct, just the pointers to pointers member. In the flex2.c you will see that the call realloc_struct is replaced with realloc_pointers to reflect this difference.

flexible array sources


END

Back to main page