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>:
| Function | Purpose |
|---|---|
| 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
| Feature | malloc | calloc | realloc |
|---|---|---|---|
| Initialization | Not initialized (garbage) | Initialized to 0 | Preserved from old block |
| Parameters | One (total bytes) | Two (count × size) | Two (pointer + new size) |
| Use Case | Quick allocation | Zeroed arrays | Resize 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 Class | Scope | Lifetime | Default Value |
|---|---|---|---|
| auto | Local (block) | Block duration | Garbage |
| register | Local (block) | Block duration | Garbage |
| static | Local or file | Program duration | 0 |
| extern | Global (all files) | Program duration | 0 |
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.
| Operator | Name | Description |
|---|---|---|
| & | AND | 1 only if both bits are 1 |
| | | OR | 1 if at least one bit is 1 |
| ^ | XOR | 1 only if bits are different |
| ~ | NOT | Inverts all bits |
| << | Left Shift | Shifts bits left (multiplies by 2) |
| >> | Right Shift | Shifts 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.
