When managing complex data records like student databases, network interfaces, or sensor telemetry in C, keeping separate scalar arrays (int id[100], char name[100][32], float temperature[100]) is an architectural nightmare. Sorting or swapping one record requires manually shuffling elements across three distinct arrays. An Array of Structures (struct Device devices[100]) solves this by grouping related fields into a single contiguous record block in memory.
Memory Architecture: Contiguous Struct Elements & Padding
In memory, an array of structures is stored as a contiguous sequence of struct blocks. However, because CPUs require data types to be aligned on natural word boundaries (e.g. 4-byte alignment for 32-bit integers), the compiler inserts invisible padding bytes between struct members:
+---------------------------------------------------------------------------------+
| RAM LAYOUT FOR struct StudentData students[2] |
+---------------------------------------------------------------------------------+
| ELEMENT 0: students[0] (Total 20 Bytes with Padding) |
| Address: 0x1000 | id (4 Bytes) : 0x00000065 |
| Address: 0x1004 | name (10 Bytes) : "Alice\0\0\0\0" |
| Address: 0x100E | PADDING (2 Bytes): [ 0x00 0x00 ] (Aligns gpa to 4-byte boundary) |
| Address: 0x1010 | gpa (4 Bytes) : 3.92 |
+---------------------------------------------------------------------------------+
| ELEMENT 1: students[1] (Total 20 Bytes with Padding) |
| Address: 0x1014 | id (4 Bytes) : 0x00000066 |
| Address: 0x1018 | name (10 Bytes) : "Bob\0\0\0\0\0\0" |
| Address: 0x1022 | PADDING (2 Bytes): [ 0x00 0x00 ] |
| Address: 0x1024 | gpa (4 Bytes) : 3.75 |
+---------------------------------------------------------------------------------+
Address Arithmetic: &students[1] = Base_Addr (0x1000) + (1 * sizeof(struct StudentData))
= 0x1000 + (1 * 20) = 0x10141. Production Implementation: Static Struct Array & qsort()
Here is a production C program declaring a static array of structures, populating data, and sorting records by GPA using POSIX qsort():
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_STUDENTS 3
typedef struct {
int id;
char name[16];
float gpa;
} Student;
// Comparator callback function for qsort (Descending by GPA)
int compare_by_gpa(const void *a, const void *b) {
const Student *s1 = (const Student *)a;
const Student *s2 = (const Student *)b;
if (s2->gpa > s1->gpa) return 1;
if (s2->gpa < s1->gpa) return -1;
return 0;
}
int main(void) {
// Compile-time struct array initialization
Student roster[MAX_STUDENTS] = {
{101, "Alice", 3.85f},
{102, "Bob", 3.50f},
{103, "Charlie", 3.95f}
};
printf("=== Original Roster ===\n");
for (size_t i = 0; i < MAX_STUDENTS; i++) {
printf("ID: %d | Name: %-10s | GPA: %.2f\n", roster[i].id, roster[i].name, roster[i].gpa);
}
// Sort struct array in-place
qsort(roster, MAX_STUDENTS, sizeof(Student), compare_by_gpa);
printf("\n=== Sorted Roster (Highest GPA First) ===\n");
for (size_t i = 0; i < MAX_STUDENTS; i++) {
printf("Rank %zu: %-10s (GPA: %.2f)\n", i + 1, roster[i].name, roster[i].gpa);
}
return 0;
}Key Insights from Struct Array Sorting:
Dot Operator (`.`) Access: Elements are accessed via
roster[i].field.roster[i]yields the struct value at indexi.Atomic In-Place Swaps: When
qsort()swaps two elements, it copies the entiresizeof(Student)byte block (24 bytes), automatically keepingid,name, andgpasynchronized.
2. Dynamic Allocation: Heap-Allocated Array of Structures
When the number of records is unknown until runtime, allocate the struct array dynamically on the Heap using malloc():
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int device_id;
int status_code;
} DeviceRecord;
int main(void) {
size_t count = 5;
// Allocate array of 5 DeviceRecord structs on Heap
DeviceRecord *devices = malloc(count * sizeof(DeviceRecord));
if (devices == NULL) {
perror("Allocation failed");
return 1;
}
// Access using pointer arrow notation vs array subscript notation
for (size_t i = 0; i < count; i++) {
(devices + i)->device_id = 1000 + (int)i; // Pointer arithmetic with ->
devices[i].status_code = 200; // Subscript notation with .
}
printf("Device 2 ID: %d | Status: %d\n", devices[2].device_id, devices[2].status_code);
free(devices); // Always release heap memory!
return 0;
}Pointer Arithmetic Equivalence:
`devices[i].field` == `(devices + i)->field`: Both expressions compute the exact same target RAM address:
(char *)base + i * sizeof(DeviceRecord).
AoS vs SoA: High-Performance Computing Considerations
In game engines, SIMD vectorization, and GPU compute shaders, traditional Array of Structures (AoS) can hinder cache locality if algorithms only read one field across millions of records. Modern high-performance systems often prefer Structure of Arrays (SoA):
+---------------------------------------------------------------------------------+
| Array of Structures (AoS) - Best for OOP & Record Lookup |
| Layout: [x0, y0, z0] [x1, y1, z1] [x2, y2, z2] |
+---------------------------------------------------------------------------------+
+---------------------------------------------------------------------------------+
| Structure of Arrays (SoA) - Best for SIMD Vectorization & GPU Shaders |
| Layout: X_Array: [x0, x1, x2] | Y_Array: [y0, y1, y2] | Z_Array: [z0, z1, z2] |
+---------------------------------------------------------------------------------+Troubleshooting & Common Pitfalls Checklist
Uninitialized Struct Pointers - Writing
DeviceRecord *dev; dev[0].id = 5;withoutmalloc()triggers a Segmentation Fault.Forgetting `sizeof(struct)` in Allocation - Allocating
malloc(N)instead ofmalloc(N * sizeof(struct))under-allocates memory and causes buffer overflow corruptions.
Arrays of structures provide a clean, type-safe mechanism for managing relational records in C while maintaining total control over hardware memory alignment.
Comments and corrections