I have a struct that I would like to keep in contiguous memory, so that I can be able to memcpy
the entire structure etc. However my structure contains an array of variable length. Now this length will be fixed for the duration of the program execution, but at compile time it is unknown. Can I get around this by over allocating memory following the struct to make room for the array?
So if I started with
struct license_plate{
char issuing_province_territory_code [2];
char* number;
}
I would need a separate malloc
for the number
so I thought of doing the following
struct license_plate_v2 {
char issuing_province_territory_code [3];
char number[1];
}
and allocate it as such
size_t sizeof_license_plate_v2( int number_length ){
return sizeof(struct license_plate_v2) + number_length * sizeof(char);
}
struct license_plate_v2* malloc_license_plate_v2( int number_length ){
return malloc( sizeof_license_plate_v2( number_length ) );
}
and then be able to iterate over an array like
struct license_plate_v2* index_license_plate_v2( struct license_plate_v2 *arr, int index, int plate_num_len ){
return arr + index * sizeof_license_plate_v2(plate_num_len);
}
void print_all( struct license_plate_v2* plates, int num_of_plates, int plate_num_len ){
for( int plate_index = 0; plate_index < num_of_plates; plate_index++ ){
struct license_plate_v2* plate = index_license_plate_v2( plates, plate_index, plate_num_len );
printf( "where: %s, plate: %s\n", plate->issuing_province_territory_code, plate->number );
}
}
Is this valid C? Is this guaranteed to work or am I using undefined behaviour? Is there any issue with byte alignment if the array is of structs? Is there a term for this? Is this the right way to achieve this sort of effect?
It seems work:
#include <stdlib.h>
int main( int argc, char** argv ) {
//these values could have from from argv for example
int num_len = 7;
struct license_plate_v2 *arr = malloc( 4 * sizeof_license_plate_v2(num_len) );
struct license_plate_v2 *arr_0 = arr + 0 * sizeof_license_plate_v2(num_len);
memcpy( arr_0->issuing_province_territory_code, "ON" , 3 * sizeof(char) );
memcpy( arr_0->number , "BFKK281" , (num_len+1) * sizeof(char) );
struct license_plate_v2 *arr_1 = arr + 1 * sizeof_license_plate_v2(num_len);
memcpy( arr_1->issuing_province_territory_code, "ON" , 3 * sizeof(char) );
memcpy( arr_1->number , "BYTR741" , (num_len+1) * sizeof(char) );
struct license_plate_v2 *arr_2 = arr + 2 * sizeof_license_plate_v2(num_len);
memcpy( arr_2->issuing_province_territory_code, "ON" , 3 * sizeof(char) );
memcpy( arr_2->number , "CAAA224" , (num_len+1) * sizeof(char) );
struct license_plate_v2 *arr_3 = arr + 3 * sizeof_license_plate_v2(num_len);
memcpy( arr_3->issuing_province_territory_code, "ON" , 3 * sizeof(char) );
memcpy( arr_3->number , "CASD431" , (num_len+1) * sizeof(char) );
print_all( arr, 4, 7 );
free( arr );
}
PS- this is a simplified example to illustrate the question, the real world problem involves something like up to millions of locations with thousands (run but not compile time constant) of data points each of which is a struct not a char
, so some of the obvious work arounds don't apply.
Structs with a flexible array member cannot be an element of an array. This is dictated in section 6.7.2.1p3 of the C standard:
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
The reason for this is that array indexing is done by pointing to a memory location that is a multiple of the size of the struct. But if the struct has a variable size, there's no way to know where the next instance of a struct is located in memory.
In your particular case, the maximum length of a license plate number is not that large, so just use a fixed size large enough to hold any value it may contain.
struct license_plate{
char issuing_province_territory_code[3];
char number[20];
}
Also, the way you set up a flexible array member with an array of size 1 is the old way of doing this before they were standardized, and it often referred to as "the struct hack". The modern way of declaring a flexible array member is with an unspecified size:
struct license_plate_v2 {
char issuing_province_territory_code [3];
char number[];
}
And sizeof(struct license_plate_v2)
does not include the flexible array member
.