C Programming

Module 5· 7 min read· 10 Questions·completed

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.

Diagram / Text
Process Heap Architecture:
Low Heap Address  ──> [ Block A (Allocated) ] [ Block B (Free) ] [ Block C (Allocated) ] ──> High Heap Address
                      ↑                      ↑
                      malloc(100)            free(ptr)
  1. Natural Alignment: The CPU requirement that data types of size kk bytes must reside at physical memory addresses divisible by kk (e.g., a 4-byte int at an address ending in 0x0, 0x4, 0x8, 0xC). Unaligned access causes hardware penalties or bus errors.
  2. Structure Padding: Extra unused bytes inserted by the compiler between structure members to preserve natural alignment.
  3. 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.
  4. Dangling Pointer: A pointer that continues referencing a heap memory address after free() has already deallocated that block.
  5. Memory Leak: Heap memory allocated via malloc/calloc that is never released with free(), and for which all pointer references have been lost.
  6. 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>)#

C
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);
FunctionInitializationOverflow GuardKey Behavior on Failure
malloc(n)Uninitialized (contains garbage bits)NoneReturns NULL. Leaves errno unchanged or sets ENOMEM
calloc(n, sz)Zeroed out (all bits set to 0)Guards against n * sz integer overflowReturns NULL if memory exhausted or multiplication overflows
realloc(ptr, sz)Preserves existing data up to min(old, new)NoneReturns NULL on failure, ORIGINAL BLOCK REMAINS VALID!
free(ptr)Deallocates block back to heapN/AIf ptr == NULL, safely performs NO operation

2.2 The Notorious realloc Memory Leak Trap#

C
// 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#

  1. Every primitive member must be placed at an offset that is a multiple of its own alignment requirement (sizeof(member)).
  2. Padding bytes are inserted before any member whose natural alignment is not yet satisfied.
  3. The total size of the structure must be an integer multiple of the largest alignment requirement among all its members (tail padding).
Diagram / Text
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:

C
// 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:

C
// 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;
};
Warning

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.

C
#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:

C
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:

  1. You CANNOT apply the address-of operator & to a bitfield member (&reg.enable is a COMPILE-TIME ERROR) because CPU addresses point to bytes, not individual bits!
  2. A bitfield cannot be declared as an array.
  3. Bitfield signedness without explicit signed/unsigned is implementation-defined. Always specify unsigned int or signed int.

7. Best Practices & Defensive Memory Management#

  1. Free What You Allocate: Every malloc / calloc call must correspond to a single, guaranteed free along every execution branch.
  2. Prevent Dangling Pointers with the Macro Pattern:
    C
    #define SAFE_FREE(ptr) do { free(ptr); (ptr) = NULL; } while(0)
    
  3. Prefer calloc When Zero Initialization is Required: Avoid manual malloc + memset(p, 0, size). calloc is 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.