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];
"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.
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.
We could have the structure like so
After the new structure has been allocated one can access the flexible array members.
THESTRUCT * ts = malloc(sizeof(THESTRUCT) + (sizeof(char**) * 10));
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().
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.
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.
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.
END