5. Memory Management, Structs & Unions
Heap allocation (malloc, calloc, realloc, free), structure padding and alignment rules, self-referential structures, unions for endianness detection, and bitfields
GATE CS & UGC NET JRF Core Subject Exam Weightage: 2–3 Direct Questions on calculating
sizeof(struct)with alignment padding, pointer realloc traps, and memory leak / dangling pointer identification.
1. Technical Jargon & The Heap Architecture#
While stack memory is managed automatically by function call frames, Heap Memory is dynamically requested by the programmer at runtime.
Process Heap Architecture:
Low Heap Address ──> [ Block A (Allocated) ] [ Block B (Free) ] [ Block C (Allocated) ] ──> High Heap Address
↑ ↑
malloc(100) free(ptr)
- Natural Alignment: The CPU requirement that data types of size bytes must reside at physical memory addresses divisible by (e.g., a 4-byte
intat an address ending in0x0,0x4,0x8,0xC). Unaligned access causes hardware penalties or bus errors. - Structure Padding: Extra unused bytes inserted by the compiler between structure members to preserve natural alignment.
- Internal vs. External Fragmentation:
- Internal Fragmentation: Unused space within an allocated memory block (e.g., structure padding or allocator chunk rounding).
- External Fragmentation: Free memory scattered in small disjoint chunks where no single chunk is large enough to satisfy an allocation request.
- Dangling Pointer: A pointer that continues referencing a heap memory address after
free()has already deallocated that block. - Memory Leak: Heap memory allocated via
malloc/callocthat is never released withfree(), and for which all pointer references have been lost. - Double Free: Calling
free()twice on the exact same memory address, which corrupts the heap allocator's internal metadata and invites severe security vulnerabilities.
2. Dynamic Memory Functions: malloc, calloc, realloc, free#
2.1 The Standard Allocator Family (<stdlib.h>)#
void* malloc(size_t size);
void* calloc(size_t num, size_t size);
void* realloc(void* ptr, size_t new_size);
void free(void* ptr);
| Function | Initialization | Overflow Guard | Key Behavior on Failure |
|---|---|---|---|
malloc(n) | Uninitialized (contains garbage bits) | None | Returns NULL. Leaves errno unchanged or sets ENOMEM |
calloc(n, sz) | Zeroed out (all bits set to 0) | Guards against n * sz integer overflow | Returns NULL if memory exhausted or multiplication overflows |
realloc(ptr, sz) | Preserves existing data up to min(old, new) | None | Returns NULL on failure, ORIGINAL BLOCK REMAINS VALID! |
free(ptr) | Deallocates block back to heap | N/A | If ptr == NULL, safely performs NO operation |
2.2 The Notorious realloc Memory Leak Trap#
// THE DANGEROUS WAY (Common bug in GATE and production):
int *p = malloc(100 * sizeof(int));
p = realloc(p, 200 * sizeof(int)); // BUG! If realloc fails and returns NULL,
// the pointer to the original 100 ints is LOST forever!
// Result: Silent Memory Leak.
// THE SAFE WAY:
int *new_p = realloc(p, 200 * sizeof(int));
if (new_p == NULL) {
// Handle allocation failure gracefully:
// 'p' is STILL valid and can be freed or used!
free(p);
exit(1);
}
p = new_p;
3. Structure Padding & Memory Alignment Rules#
Why is sizeof(struct) almost always larger than the sum of its constituent members?
3.1 The Alignment Algorithm#
- Every primitive member must be placed at an offset that is a multiple of its own alignment requirement (
sizeof(member)). - Padding bytes are inserted before any member whose natural alignment is not yet satisfied.
- The total size of the structure must be an integer multiple of the largest alignment requirement among all its members (tail padding).
Example:
struct Example {
char a; // 1 byte
// 3 bytes PADDING inserted here!
int b; // 4 bytes (must align to multiple of 4)
char c; // 1 byte
// 3 bytes TAIL PADDING to round up to multiple of 4!
};
Memory Layout (Total Size = 12 bytes, NOT 6 bytes!):
[ a ] [pad] [pad] [pad] [ b0 ] [ b1 ] [ b2 ] [ b3 ] [ c ] [pad] [pad] [pad]
0 1 2 3 4 5 6 7 8 9 10 11
3.2 Optimization: Member Reordering to Minimize Padding#
By arranging structure members in descending order of size, you eliminate internal padding:
// Suboptimal (12 bytes):
struct Bad {
char a; // 1B + 3B padding
int b; // 4B
char c; // 1B + 3B tail padding
}; // Total: 12 bytes
// Optimized (8 bytes - 33% memory savings!):
struct Good {
int b; // 4B
char a; // 1B
char c; // 1B
// 2B tail padding (rounds to multiple of 4)
}; // Total: 8 bytes
4. Self-Referential Structures: Foundation of Linked Data Structures#
A structure that contains a pointer to an instance of its own type is called a Self-Referential Structure:
// Singly Linked List Node:
struct Node {
int data; // 4 bytes
struct Node *next; // 8 bytes (pointer to next node on heap)
};
// Binary Tree Node:
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
};
GATE Trap: Direct Embedding vs. Pointer!
struct Node { int data; struct Node next; }; is a COMPILE-TIME ERROR.
A structure cannot contain an instance of itself because its size would be infinite! It can only contain a pointer to itself (struct Node *next), which has a fixed, known pointer size (4 or 8 bytes).
5. Unions & Hardware Endianness Detection#
A union is a user-defined type where all members share the exact same memory location. The total size of the union is determined by the size of its largest member.
#include <stdio.h>
// Using a union to inspect CPU Endianness (Byte Order):
union EndianChecker {
unsigned int value;
unsigned char bytes[sizeof(unsigned int)];
};
int main(void) {
union EndianChecker test;
test.value = 0x01020304;
// In Little-Endian: Least significant byte (0x04) is stored at lowest address
// In Big-Endian: Most significant byte (0x01) is stored at lowest address
if (test.bytes[0] == 0x04) {
printf("Architecture: Little-Endian\n"); // x86, ARM (usually)
} else if (test.bytes[0] == 0x01) {
printf("Architecture: Big-Endian\n"); // Network byte order, SPARC
}
return 0;
}
6. Bitfields: Syntax & Hardware Register Mapping#
Bitfields allow packing integer values into specific bit lengths to map directly onto hardware control registers or save memory:
struct Register {
unsigned int enable : 1; // Exactly 1 bit (0 or 1)
unsigned int mode : 3; // Exactly 3 bits (0 to 7)
unsigned int channel : 4; // Exactly 4 bits (0 to 15)
};
[!CRITICAL] GATE Bitfield Rules:
- You CANNOT apply the address-of operator
&to a bitfield member (®.enableis a COMPILE-TIME ERROR) because CPU addresses point to bytes, not individual bits!- A bitfield cannot be declared as an array.
- Bitfield signedness without explicit
signed/unsignedis implementation-defined. Always specifyunsigned intorsigned int.
7. Best Practices & Defensive Memory Management#
- Free What You Allocate:
Every
malloc/calloccall must correspond to a single, guaranteedfreealong every execution branch. - Prevent Dangling Pointers with the Macro Pattern:
C
#define SAFE_FREE(ptr) do { free(ptr); (ptr) = NULL; } while(0) - Prefer
callocWhen Zero Initialization is Required: Avoid manualmalloc+memset(p, 0, size).callocis optimized and includes internal overflow multiplication checks.
Practice Quiz
Test your understanding with step-by-step solutions
Practice Quiz
10 questions · 90s per question
Each question has a 90-second time limit. Unanswered questions will be auto-submitted when time runs out.