C - Flexible Array Members in C
A Flexible Array Member (FAM) is a special feature in C that allows a structure to contain an array whose size is not specified when the structure is declared. Instead of deciding the array size in advance, the required amount of memory can be allocated dynamically when the program runs.
Flexible array members are particularly useful when a structure needs to store a fixed set of information along with a variable amount of data. They were standardized in C99 and are commonly used for memory-efficient data structures.
1. Basic Syntax
A flexible array member is declared as the last member of a structure, and its size is left empty.
struct Student {
int roll_number;
int marks[];
};
Here, marks[] is a flexible array member.
The compiler does not allocate space for the elements of marks as part of the ordinary structure object. Instead, additional memory can be allocated after the structure when required.
A flexible array member must satisfy some important rules:
-
It must be the last member of the structure.
-
Its type must be an array type.
-
The array must not have a specified size.
-
The structure must contain at least one other named member.
-
The flexible array member does not contribute its elements to the ordinary size of the structure.
2. Why Flexible Array Members Are Useful
Suppose a program needs to store information about a student and an unknown number of marks.
Without a flexible array member, you might define:
struct Student {
int roll_number;
int marks[100];
};
This reserves space for 100 marks even if a student has only 5 marks.
This approach can waste memory.
A flexible array member allows the program to allocate only the amount of memory actually required.
struct Student {
int roll_number;
int marks[];
};
If a student has five marks, the program can allocate enough memory for the structure plus five integers. If another student has ten marks, it can allocate enough memory for ten integers.
This makes the data structure more flexible.
3. Dynamic Memory Allocation
Flexible array members are generally used together with dynamic memory allocation functions such as malloc().
Consider this example:
#include <stdio.h>
#include <stdlib.h>
struct Student {
int roll_number;
int marks[];
};
int main() {
int count = 5;
struct Student *student;
student = malloc(sizeof(struct Student) +
count * sizeof(int));
if (student == NULL) {
return 1;
}
student->roll_number = 101;
for (int i = 0; i < count; i++) {
student->marks[i] = (i + 1) * 10;
}
printf("Roll Number: %d\n", student->roll_number);
for (int i = 0; i < count; i++) {
printf("Mark %d: %d\n", i + 1, student->marks[i]);
}
free(student);
return 0;
}
In this example, the structure contains a fixed member called roll_number and a flexible array member called marks.
The expression:
sizeof(struct Student) + count * sizeof(int)
calculates the amount of memory needed for the structure and five integer elements.
4. Understanding the Memory Layout
Consider:
struct Student {
int roll_number;
int marks[];
};
Conceptually, the allocated memory looks like this:
+----------------------+
| roll_number |
+----------------------+
| marks[0] |
+----------------------+
| marks[1] |
+----------------------+
| marks[2] |
+----------------------+
| marks[3] |
+----------------------+
| marks[4] |
+----------------------+
The flexible array elements are stored immediately after the normal structure members.
If five integers are requested, the allocated block contains enough space for the fixed portion of the structure followed by five integers.
This is one of the major advantages of flexible array members: the structure and its variable-sized data can occupy one contiguous memory block.
5. Correct Memory Allocation
A common pattern is:
struct Data {
int size;
int values[];
};
Memory can be allocated as:
int n = 10;
struct Data *data =
malloc(sizeof(struct Data) + n * sizeof(int));
The first part:
sizeof(struct Data)
provides memory for the fixed portion.
The second part:
n * sizeof(int)
provides memory for the flexible array.
Therefore:
sizeof(struct Data) + n * sizeof(int)
represents the total requested allocation.
6. Accessing Flexible Array Members
Once the memory has been allocated correctly, the flexible array can be accessed just like an ordinary array.
data->values[0] = 100;
data->values[1] = 200;
data->values[2] = 300;
A loop can also be used:
for (int i = 0; i < n; i++) {
data->values[i] = i * 10;
}
The compiler allows array-style indexing, while the actual storage comes from the dynamically allocated memory block.
7. Flexible Array Members and sizeof
One important characteristic of flexible array members is that their elements are not included in the value returned by sizeof for the structure type.
For example:
struct Data {
int size;
int values[];
};
The expression:
sizeof(struct Data)
accounts for the fixed portion of the structure, including any required padding, but not storage for the elements of values.
Therefore, if you need space for 10 integers, you must explicitly include that space in the allocation:
malloc(sizeof(struct Data) + 10 * sizeof(int));
8. Flexible Array Member Must Be the Last Member
The flexible array member must appear at the end of the structure.
Correct:
struct Example {
int id;
char data[];
};
Incorrect:
struct Example {
char data[];
int id;
};
The reason is that the flexible array can occupy the remaining dynamically allocated space after the fixed members. If another member followed it, the compiler would not have a well-defined location for that member.
9. Flexible Array Members with Structures Containing Multiple Fields
A flexible array member does not have to be the only variable-sized information associated with the structure.
For example:
struct Employee {
int id;
float salary;
char name[];
};
Memory can be allocated according to the required name length:
int length = 20;
struct Employee *employee =
malloc(sizeof(struct Employee) + length * sizeof(char));
The resulting memory contains:
+----------------------+
| id |
+----------------------+
| salary |
+----------------------+
| name[0] |
+----------------------+
| name[1] |
+----------------------+
| ... |
+----------------------+
| name[19] |
+----------------------+
This can be useful for storing variable-length strings along with related information.
10. Example Using a Variable-Length String
Here is another practical example:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Message {
int length;
char text[];
};
int main() {
const char *source = "Hello, C Programming";
int length = strlen(source);
struct Message *message =
malloc(sizeof(struct Message) + (length + 1) * sizeof(char));
if (message == NULL) {
return 1;
}
message->length = length;
strcpy(message->text, source);
printf("Length: %d\n", message->length);
printf("Message: %s\n", message->text);
free(message);
return 0;
}
The + 1 is important here because a C string requires an additional byte for the null character '\0'.
11. Advantages of Flexible Array Members
Flexible array members provide several benefits.
Efficient memory usage:
The program can allocate memory according to the actual amount of data required rather than reserving a fixed maximum size.
Single memory allocation:
The structure and its variable-sized data can be stored in one dynamically allocated memory block.
Better locality:
Because the data is stored immediately after the structure, accessing the related data can be convenient and memory-efficient.
Useful for variable-sized objects:
They are suitable for packets, messages, buffers, records, and other structures whose data size is determined at runtime.
12. Important Precautions
Flexible array members require careful memory management.
The allocation must provide enough space for the desired number of elements:
malloc(sizeof(struct Data) + n * sizeof(int));
The program must not access more elements than were allocated.
For example, if space was allocated for five integers:
n = 5;
the program must not access:
data->values[5];
because valid indexes are:
0
1
2
3
4
Accessing beyond the allocated memory can result in undefined behavior.
The allocated memory should also be released when it is no longer needed:
free(data);
13. Difference Between a Fixed Array and a Flexible Array Member
A fixed array has a predetermined size:
struct Student {
int marks[10];
};
Every instance has space for ten integers.
A flexible array member has no predetermined element count:
struct Student {
int marks[];
};
The required storage is determined when memory is dynamically allocated.
Therefore, fixed arrays are useful when the maximum or exact size is known, while flexible array members are useful when the amount of associated data is determined at runtime.
14. Common Applications
Flexible array members are commonly useful in situations involving variable-sized data, such as:
-
Network packet representations
-
Dynamic buffers
-
Variable-length messages
-
Database records
-
Custom memory pools
-
Serialization and deserialization structures
-
File-format structures
-
Runtime-generated data containers
For example, a network message could contain a fixed header followed by a payload whose size changes from message to message.
struct Packet {
int packet_size;
char payload[];
};
The program can then allocate enough memory for the header and the required payload.
15. Summary
A Flexible Array Member is an unsized array placed at the end of a C structure. It allows programmers to create structures that contain a fixed portion followed by a variable amount of data.
The general pattern is:
struct Data {
int count;
int values[];
};
Memory is then allocated using:
struct Data *data =
malloc(sizeof(struct Data) + count * sizeof(int));
The main idea is that the structure provides the fixed information, while additional dynamically allocated memory provides storage for the flexible array.
Flexible array members are especially valuable when the amount of data cannot be known at compile time and when efficient, contiguous memory organization is required.