A C struct groups named fields of different types into a single composite type. Arrays are fixed-size contiguous sequences of a single type, and decay to a pointer to their first element in most expressions. Strings in C are null-terminated char arrays — the length is not stored, just a sentinel zero byte. Function pointers enable callbacks and polymorphism-like dispatch patterns.
C has no classes. When you need to group related data — a person's name and age, a network packet's header and payload — you use a struct. When you need a sequence of the same type, you use an array. These two constructs, combined with pointers, build every data structure in C: linked lists, trees, hash tables, queues.
Structs: definition and usage
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* struct definition */
typedef struct {
char name[64];
int age;
double balance;
} Account; /* typedef lets us use Account instead of struct Account */
/* Functions that operate on structs receive a pointer — avoids copying */
void deposit(Account *acc, double amount) {
acc->balance += amount; /* -> dereferences and accesses field */
/* equivalent to: (*acc).balance += amount */
}
void print_account(const Account *acc) { /* const: promise not to modify */
printf("Name: %s, Age: %d, Balance: %.2f\n",
acc->name, acc->age, acc->balance);
}
int main(void) {
/* Stack allocation: brace initialiser */
Account alice = {"Alice", 30, 1000.00};
deposit(&alice, 500.0);
print_account(&alice); /* Name: Alice, Age: 30, Balance: 1500.00 */
/* Heap allocation */
Account *bob = malloc(sizeof(Account));
if (!bob) return 1;
strncpy(bob->name, "Bob", sizeof(bob->name) - 1);
bob->name[sizeof(bob->name) - 1] = '\0'; /* ensure null termination */
bob->age = 25;
bob->balance = 200.0;
print_account(bob);
free(bob);
return 0;
}Arrays and strings
#include <stdio.h>
#include <string.h>
int main(void) {
/* Fixed-size array: size must be a compile-time constant (or C99 VLA) */
int nums[5] = {10, 20, 30, 40, 50};
printf("sizeof(nums) = %zu bytes\n", sizeof(nums)); /* 20 (5 * 4) */
printf("element count = %zu\n", sizeof(nums) / sizeof(nums[0])); /* 5 */
/* Strings: null-terminated char arrays */
char greeting[32] = "Hello"; /* {'H','e','l','l','o','\0', ...} */
printf("strlen = %zu\n", strlen(greeting)); /* 5 — does NOT count '\0' */
printf("sizeof = %zu\n", sizeof(greeting)); /* 32 — total buffer size */
/* String functions — always use strncpy, not strcpy (bounds safety) */
char buf[8];
strncpy(buf, "Codex", sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0'; /* manual null termination */
/* 2D array: row-major storage */
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
printf("matrix[1][2] = %d\n", matrix[1][2]); /* 7 */
/* Array decay: passes as pointer — sizeof DOES NOT work in the function */
/* Always pass the size separately: void process(int *arr, size_t n) */
return 0;
}Function pointers: callbacks and dispatch
#include <stdio.h>
#include <stdlib.h>
/* Function pointer type: pointer to a function taking two ints, returning int */
typedef int (*CompareFunc)(const void *, const void *);
int compare_int_asc(const void *a, const void *b) {
return (*(int*)a - *(int*)b);
}
int compare_int_desc(const void *a, const void *b) {
return (*(int*)b - *(int*)a);
}
int main(void) {
int arr[] = {5, 2, 8, 1, 9, 3};
int n = sizeof(arr) / sizeof(arr[0]);
/* qsort accepts a function pointer — the classic C callback */
qsort(arr, n, sizeof(int), compare_int_asc);
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n"); /* 1 2 3 5 8 9 */
qsort(arr, n, sizeof(int), compare_int_desc);
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n"); /* 9 8 5 3 2 1 */
/* Storing a function pointer in a variable */
CompareFunc cmp = compare_int_asc;
int a = 3, b = 5;
printf("cmp result: %d\n", cmp(&a, &b)); /* negative: a < b */
return 0;
}Struct padding and memory layout
The C compiler inserts padding bytes between struct fields to ensure each field is aligned to its natural alignment (typically its size). A double (8 bytes) must start at an 8-byte boundary; an int (4 bytes) at a 4-byte boundary. The total struct size is padded to a multiple of its largest field's alignment. This means field order affects struct size — reordering fields to put large types first can significantly reduce padding.
#include <stdio.h>
/* Inefficient: 24 bytes due to padding */
struct Padded {
char a; /* 1 byte + 7 padding */
double b; /* 8 bytes */
char c; /* 1 byte + 7 padding */
}; /* total: 24 bytes */
/* Efficient: 16 bytes — largest fields first */
struct Packed {
double b; /* 8 bytes */
char a; /* 1 byte */
char c; /* 1 byte + 6 padding */
}; /* total: 16 bytes */
int main(void) {
printf("Padded: %zu bytes\n", sizeof(struct Padded)); /* 24 */
printf("Packed: %zu bytes\n", sizeof(struct Packed)); /* 16 */
return 0;
}Linked lists: structs with self-referential pointers
C's only way to build dynamic data structures is through structs containing pointers to the same struct type. This self-referential pattern underlies linked lists, trees, graphs, and hash tables in C.
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int value;
struct Node *next; /* must use struct Node here — typedef not yet complete */
} Node;
Node *node_new(int value) {
Node *n = malloc(sizeof(Node));
if (n) { n->value = value; n->next = NULL; }
return n;
}
void list_print(const Node *head) {
for (const Node *n = head; n != NULL; n = n->next)
printf("%d -> ", n->value);
printf("NULL\n");
}
void list_free(Node *head) {
while (head) {
Node *next = head->next;
free(head);
head = next;
}
}
int main(void) {
Node *head = node_new(1);
head->next = node_new(2);
head->next->next = node_new(3);
list_print(head); /* 1 -> 2 -> 3 -> NULL */
list_free(head);
return 0;
}int arr[5], sizeof(arr) is 20 (correct). Pass that array to another function and inside that function, sizeof(arr) is 4 or 8 (the pointer size — wrong). Arrays decay to pointers when passed to functions. Always pass the array length as a separate parameter: void process(int *arr, size_t n).strlen walks the string byte by byte counting until it hits \0. This makes it O(n). If you modify a string and forget the null terminator, strlen and most string functions will walk past the end of your buffer — buffer overread, undefined behaviour, potential crash or exploit. Always ensure \0 at the end when building strings manually.Account b = a copies every byte of a into b. This is a shallow copy — if the struct contains pointers, the pointers are copied but not the pointed-to data. Both a.name and b.name now point to the same memory (if name were a pointer). For structs with pointer fields, deep copy requires manually duplicating the pointed-to data.Flexible array members and designated initialisers (C99+)
C99 introduced flexible array members — a struct's last field can be an array of unspecified size: struct Buffer { size_t len; char data[]; };. You allocate sizeof(Buffer) + n bytes and the data array uses the extra space. This is the pattern used for variable-length protocol messages in the Linux kernel and network stack. C99 also introduced designated initialisers: Account a = { .name = "Alice", .age = 30 } — field order in the initialiser doesn't need to match declaration order, and unspecified fields are zero-initialised. C23 further refined these.
vtable pattern: function pointer tables for polymorphism
C++'s vtable is just a struct of function pointers. The same pattern is used extensively in C: a struct Operations containing function pointers serves as a virtual dispatch table. The Linux kernel uses this pattern throughout — struct file_operations for the VFS, struct net_device_ops for network drivers. Each driver provides its own file_operations struct with its implementation of open, read, write, ioctl. The VFS calls through the function pointer — a callback that dispatches to the right driver implementation. This is C's mechanism for runtime polymorphism without classes.
ISO/IEC 9899:2018 (C17). Sections 6.2.5 (types), 6.7.2.1 (struct/union), 6.7.9 (initialisation). Kernighan, B. W. & Ritchie, D. M. (1988). The C Programming Language (2nd ed.). Chapter 6 (Structures). cppreference.com/w/c/language/struct.