Dynamic Memory Allocation in C

When a variable is declared in C, memory is allocated at compile time — the size is fixed and cannot change. But many real programs need memory that grows or shrinks based on user input at runtime. Dynamic Memory Allocation allows requesting memory from the operating system while the program is running.

C provides four functions for dynamic memory allocation, all found in <stdlib.h>:

FunctionPurpose
malloc()Allocate memory block (uninitialized)
calloc()Allocate memory block (initialized to 0)
realloc()Resize previously allocated memory
free()Release allocated memory back to OS

malloc() — Memory Allocation


pointer = (cast_type *) malloc(size_in_bytes);

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int n;
    printf("How many numbers? ");
    scanf("%d", &n);

    int *arr = (int *) malloc(n * sizeof(int));

    if (arr == NULL)
    {
        printf("Memory allocation failed!\n");
        return 1;
    }

    for (int i = 0; i < n; i++)
    {
        arr[i] = (i + 1) * 10;
    }

    printf("Values: ");
    for (int i = 0; i < n; i++)
        printf("%d ", arr[i]);
    printf("\n");

    free(arr);  // release memory
    return 0;
}

Sample Output


How many numbers? 4
Values: 10 20 30 40

calloc() — Contiguous Allocation

Like malloc() but initializes all bytes to zero.


pointer = (cast_type *) calloc(num_elements, element_size);

int *arr = (int *) calloc(5, sizeof(int));
// All 5 elements initialized to 0

realloc() — Resize Memory

Resizes previously allocated memory. Can expand or shrink.


int *arr = (int *) malloc(3 * sizeof(int));
arr = (int *) realloc(arr, 6 * sizeof(int));  // expand to 6 elements

free() — Release Memory

Always call free() after dynamic allocation is no longer needed. Failure to do so causes a memory leak.


free(ptr);
ptr = NULL;  // good practice to avoid dangling pointer

malloc vs calloc vs realloc

Featuremalloccallocrealloc
InitializationNot initialized (garbage)Initialized to 0Preserved from old block
ParametersOne (total bytes)Two (count × size)Two (pointer + new size)
Use CaseQuick allocationZeroed arraysResize existing block

Storage Classes in C

A storage class defines the lifetime (how long the variable exists), scope (where it is accessible), and default value of a variable. C has four storage classes:

1. auto

The default storage class for local variables. Auto variables are created when the block is entered and destroyed when the block exits.


void show() {
    auto int x = 5;  // 'auto' is implicit, same as: int x = 5;
    printf("%d\n", x);
}

2. register

Suggests the compiler store the variable in a CPU register instead of RAM for faster access. Useful for loop counters.


register int counter = 0;  // hint to store in CPU register

3. static

A static local variable retains its value between function calls. A static global variable is limited to the current file.


void count() {
    static int c = 0;  // retains value between calls
    c++;
    printf("Count: %d\n", c);
}
// count() → 1, count() → 2, count() → 3 ...

4. extern

Refers to a global variable defined in another file. Used to share variables across multiple files.


extern int globalScore;  // defined in another file
Storage ClassScopeLifetimeDefault Value
autoLocal (block)Block durationGarbage
registerLocal (block)Block durationGarbage
staticLocal or fileProgram duration0
externGlobal (all files)Program duration0

Preprocessor Directives in C

Preprocessor directives are instructions that are processed before compilation. They begin with the # symbol and do not end with a semicolon.

#include — Header Files


#include <stdio.h>      // system header file
#include "myfile.h"     // user-defined header file

#define — Macro Definition


#define PI 3.14159
#define MAX 100
#define SQUARE(x) ((x) * (x))  // function-like macro

float area = PI * SQUARE(5);  // expands to: 3.14159 * ((5) * (5))

#undef — Remove a Macro


#define LIMIT 50
#undef LIMIT  // removes LIMIT definition

Conditional Compilation


#define DEBUG 1

#ifdef DEBUG
    printf("Debug: value = %d\n", x);  // only compiled if DEBUG is defined
#endif

#ifndef LIMIT
    #define LIMIT 100  // define only if not already defined
#endif

#pragma — Compiler Instructions


#pragma once   // include file only once (prevents duplicate inclusion)

typedef in C

typedef creates an alias (nickname) for an existing data type. This makes code more readable and portable.


#include <stdio.h>

typedef unsigned int uint;
typedef float Celsius;

int main()
{
    uint count = 100;
    Celsius temp = 36.6;

    printf("Count: %u\n", count);
    printf("Temp:  %.1f\n", temp);

    return 0;
}

typedef with structures (covered in the Structures topic) is also very common:


typedef struct {
    int x;
    int y;
} Point;

Point p1 = {3, 7};  // No need to write 'struct Point'

enum in C

An enum (enumeration) is a user-defined type that assigns meaningful names to a set of integer constants. It improves code readability by replacing magic numbers with descriptive names.

Syntax


enum enum_name { value1, value2, value3, ... };

By default, values start from 0 and increment by 1.


#include <stdio.h>

enum Day { MON, TUE, WED, THU, FRI, SAT, SUN };
// MON=0, TUE=1, WED=2, ..., SUN=6

int main()
{
    enum Day today = WED;

    if (today == WED)
        printf("It's Wednesday!\n");

    printf("Day number: %d\n", today);  // 2

    return 0;
}

Custom Starting Values


enum Month { JAN=1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC };
// JAN=1, FEB=2, MAR=3, ...

Bitwise Operators in Detail

Bitwise operators work at the binary bit level. They are essential for systems programming, embedded systems, hardware interface code, and flags.

OperatorNameDescription
&AND1 only if both bits are 1
|OR1 if at least one bit is 1
^XOR1 only if bits are different
~NOTInverts all bits
<<Left ShiftShifts bits left (multiplies by 2)
>>Right ShiftShifts bits right (divides by 2)

#include <stdio.h>

int main()
{
    int a = 12;   // binary: 1100
    int b = 10;   // binary: 1010

    printf("a & b  = %d\n", a & b);   // 1000 = 8
    printf("a | b  = %d\n", a | b);   // 1110 = 14
    printf("a ^ b  = %d\n", a ^ b);   // 0110 = 6
    printf("~a     = %d\n", ~a);      // inverts bits = -13
    printf("a << 1 = %d\n", a << 1); // 11000 = 24 (×2)
    printf("a >> 1 = %d\n", a >> 1); // 0110  = 6  (÷2)

    return 0;
}

Output


a & b  = 8
a | b  = 14
a ^ b  = 6
~a     = -13
a << 1 = 24
a >> 1 = 6

Practical Use — Check if Number is Even/Odd Using Bitwise AND


if (num & 1)
    printf("Odd\n");
else
    printf("Even\n");
// Faster than using % operator

Summary

Dynamic memory allocation (malloc, calloc, realloc, free) allows programs to manage memory at runtime — essential for variable-sized data. Storage classes control the lifetime and visibility of variables. Preprocessor directives like #define and #ifdef give control over compilation. typedef creates readable aliases for types, while enum gives meaningful names to integer constants. Bitwise operators enable low-level bit manipulation, critical for systems programming and performance-sensitive applications. Together, these features give C its powerful, flexible, and efficient character.

Leave a Comment

Your email address will not be published. Required fields are marked *