C ProgrammingUnit 712 min read
Pointers and Dynamic Memory Allocation: Memory Manipulation and Efficiency
Unit 7 of C Programming: Explores pointers (variables holding memory addresses), their arithmetic, dynamic memory allocation (malloc, free), and their role in efficiency, arrays, and functions, with real-world applications in data structures and system programming.
TAKEAWAYS:
- A pointer is a variable that stores a memory address, enabling indirect access to data and efficient memory operations.
- Pointer arithmetic allows traversal of arrays and linked structures without index tracking.
- Dynamic memory allocation (
malloc,calloc,realloc) allocates memory at runtime, critical for scalable programs like databases or game engines. - Double pointers and pointers to functions enable advanced operations like sorting with callbacks or modifying function pointers in embedded systems.
- Memory leaks and dangling pointers are common pitfalls; proper
free()and null checks are mandatory. - Arrays and pointers are interchangeable in C due to their contiguous memory layout, a key optimization in low-level programming.
1. Introduction to Pointers
A pointer is a variable that holds the memory address of another variable. It allows indirect access to data, enabling efficient operations like dynamic memory management and complex data structures.
Why Use Pointers?
- Efficiency: Avoid copying large data structures (e.g., arrays, structures).
- Flexibility: Modify data in functions without returning values (e.g., swapping two numbers).
- Dynamic Memory: Allocate memory at runtime (e.g., linked lists, trees).
- Low-Level Control: Direct hardware interaction (e.g., embedded systems, device drivers).
Pointer Declaration and Initialization
A pointer is declared using the * operator. It must be initialized to a valid address or NULL to avoid undefined behavior.
flowchart TD
A["int *ptr;"] --> B["Uninitialized pointer (dangerous)"]
B --> C["int x = 10;"]
C --> D["ptr = &x;"] --> E["ptr now holds address of x"]Example: Declaration and Initialization
#include <stdio.h>
int main() {
int num = 42;
int *ptr = # // ptr holds address of num
printf("Value of num: %d\n", num); // 42
printf("Address of num: %p\n", &num); // Memory address
printf("Value of ptr: %p\n", ptr); // Same as &num
printf("Value pointed by ptr: %d\n", *ptr); // 42 (dereferencing)
return 0;
}
Output:
Value of num: 42
Address of num: 0x7ffd42a1b2ac
Value of ptr: 0x7ffd42a1b2ac
Value pointed by ptr: 42
Key Concepts
| Term | Definition | Example |
|---|---|---|
| Pointer Variable | Stores memory address of another variable. | int *ptr; |
| Dereferencing | Accesses the value at the address stored in a pointer. | *ptr = 10; |
| Address-of Operator | Returns the memory address of a variable (&). |
ptr = # |
| Null Pointer | Pointer initialized to NULL (no valid address). |
int *ptr = NULL; |
2. Pointer Arithmetic
Pointers can be incremented/decremented to traverse memory. The step size depends on the data type:
int *ptr: +4 bytes (32-bit system) or +8 bytes (64-bit).char *ptr: +1 byte (sincecharis 1 byte).
flowchart TD
A["int arr[3] = {10, 20, 30}"] --> B["&arr[0] (address of first element)"]
B --> C["&arr[1] = &arr[0] + sizeof(int)"]
C --> D["&arr[2] = &arr[0] + 2 * sizeof(int)"]
D --> E["ptr++ moves by sizeof(int) bytes"]
E --> F["Example: 32-bit system → +4 bytes per step"]Example: Traversing an Array with Pointers
#include <stdio.h>
int main() {
int arr[3] = {10, 20, 30};
int *ptr = arr; // ptr points to arr[0]
for (int i = 0; i < 3; i++) {
printf("arr[%d] = %d\n", i, *(ptr + i)); // Dereference ptr+i
}
return 0;
}
Output:
arr[0] = 10
arr[1] = 20
arr[2] = 30
3. Pointers and Arrays
Arrays and pointers are interchangeable in C because arrays decay into pointers to their first element.
flowchart TD
A["int arr[3] = {1, 2, 3}"] --> B["arr == &arr[0] (array decays to pointer)"]
B --> C["ptr = arr; // ptr now holds address of arr[0]"]
C --> D["*(ptr + 1) == arr[1] (equivalent to arr[1])"]
D --> E["*(ptr + i) == arr[i] (general rule)"]Example: Passing Arrays to Functions Using Pointers
#include <stdio.h>
void printArray(int *arr, int size) {
for (int i = 0; i < size; i++) {
printf("%d ", *(arr + i));
}
}
int main() {
int arr[3] = {10, 20, 30};
printArray(arr, 3); // Passes pointer to arr
return 0;
}
Output:
10 20 30
4. Pointers to Pointers (Double Pointers)
A double pointer holds the address of another pointer. Used for:
- Modifying pointers inside functions.
- Dynamic memory allocation (e.g.,
mallocreturns a pointer, which may need modification).
flowchart TD
A["int x = 5"] --> B["int *ptr = &x"]
B --> C["int **dptr = &ptr"]
C --> D["**dptr = 10; modifies x"]Example: Using Double Pointers to Modify a Pointer
#include <stdio.h>
void modifyPointer(int **ptr) {
**ptr = 100; // Modifies the original variable
}
int main() {
int x = 5;
int *ptr = &x;
modifyPointer(&ptr); // Pass address of ptr
printf("x = %d\n", x); // Output: 100
return 0;
}
5. Dynamic Memory Allocation
C provides runtime memory allocation using:
malloc: Allocates uninitialized memory.calloc: Allocates initialized memory (zeros).realloc: Resizes previously allocated memory.free: Deallocates memory to prevent leaks.
Example: Dynamic Array Allocation
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr = (int*)malloc(3 * sizeof(int)); // Allocate 3 ints
if (arr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
arr[0] = 1; arr[1] = 2; arr[2] = 3;
printf("Array elements: %d %d %d\n", arr[0], arr[1], arr[2]);
free(arr); // Free memory
return 0;
}
Output:
Array elements: 1 2 3
Common Pitfalls
| Pitfall | Explanation | Solution |
|---|---|---|
| Memory Leak | Forgetting to free() allocated memory. |
Always free() after use. |
| Dangling Pointer | Pointer points to freed memory. | Set to NULL after free(). |
| Null Pointer | Dereferencing NULL causes a crash. |
Check if (ptr != NULL) before use. |
6. Pointers to Functions
A pointer to a function stores the address of a function, enabling:
- Callbacks (e.g., sorting with custom comparators).
- Function pointers in event-driven systems (e.g., GUI callbacks).
flowchart TD
A["int add(int a, int b) { return a + b; }"] --> B["int (*funcPtr)(int, int) = &add"]
B --> C["funcPtr(2, 3) → calls add(2, 3)"]
C --> D["Result: 5 (stored in return value)"]Example: Function Pointer
#include <stdio.h>
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int main() {
int (*operation)(int, int) = add; // Function pointer
printf("Result: %d\n", operation(5, 3)); // 8
operation = subtract; // Switch to subtract
printf("Result: %d\n", operation(5, 3)); // 2
return 0;
}
Output:
Result: 8
Result: 2
7. Pointers and Strings
Strings in C are null-terminated character arrays. Pointers can traverse strings efficiently.
Example: String Length Using Pointers
#include <stdio.h>
int strlen(char *str) {
int len = 0;
while (*str != '\0') {
len++;
str++; // Move pointer
}
return len;
}
int main() {
char str[] = "hello";
printf("Length: %d\n", strlen(str)); // 5
return 0;
}
Output:
Length: 5
In the Real World
eSewa/Khalti (Digital Wallets)
- Idea: Dynamic memory allocation is used to handle variable-length transaction records (e.g., storing user balances, transaction histories).
- How: When a user adds a new transaction, the system dynamically allocates memory for the record (e.g., using
malloc) and appends it to a linked list of transactions. This avoids fixed-size arrays and ensures scalability.
Daraz (E-commerce Platform)
- Idea: Pointers to functions are used in order processing pipelines.
- How: Daraz uses function pointers to route orders to different handlers (e.g.,
process_payment(),update_inventory()). This modular design allows adding new features (e.g., discounts) without rewriting core logic.
NTC/Ncell (Telecom Networks)
- Idea: Double pointers manage call routing tables dynamically.
- How: Telecom systems use double pointers to update routing tables in real time. For example, when a new cell tower is added, the system modifies the pointer-to-pointer structure to reflect the updated network topology.
Worked Example: Daraz Order Queue Suppose Daraz uses a queue to manage orders. Each order is stored in a dynamically allocated node:
typedef struct Order {
int id;
struct Order *next;
} Order;
Order *front = NULL, *rear = NULL;
void enqueue(int id) {
Order *newOrder = (Order*)malloc(sizeof(Order));
newOrder->id = id;
newOrder->next = NULL;
if (rear == NULL) {
front = rear = newOrder;
} else {
rear->next = newOrder;
rear = newOrder;
}
}
State After Enqueueing Order 101:
front --> [101] --> NULL
rear
When Order 101 is processed, front moves to the next node, and memory is freed to prevent leaks.
8. Exam Tips
Pointer Declaration:
- Always initialize pointers to
NULLor a valid address. Uninitialized pointers lead to undefined behavior. - Example:
int *ptr = NULL;(safe) vs.int *ptr;(dangerous).
- Always initialize pointers to
Dereferencing:
*ptraccesses the value at the address stored inptr.- Common mistake: Forgetting the
*when printing (printf("%d", ptr)prints an address, not the value).
Dynamic Memory:
- Check for
malloc/callocfailures (if (ptr == NULL)). - Always
free()memory to avoid leaks. Set pointers toNULLafter freeing.
- Check for
Arrays vs. Pointers:
arr[i]is equivalent to*(arr + i).- When passing arrays to functions, use
int *arr(notint arr[]), as arrays decay to pointers.
Double Pointers:
- Used to modify pointers inside functions (e.g., swapping two pointers).
- Example:
void swap(int **a, int **b) { int *temp = *a; *a = *b; *b = temp; }
Function Pointers:
- Syntax:
return_type (*ptr)(args) = &function; - Useful for callbacks (e.g., sorting with
qsort).
- Syntax:
Common Pitfalls:
- Dangling Pointers: Accessing freed memory (e.g.,
free(ptr); *ptr = 5;is undefined). - Memory Leaks: Forgetting to
free()allocated memory (e.g., in loops). - Null Pointer Dereference:
if (ptr != NULL) { *ptr = 10; }
- Dangling Pointers: Accessing freed memory (e.g.,
Sample Exam Question: Write a program to find the smallest element in an array using pointers. Solution:
#include <stdio.h>
int main() {
int arr[] = {5, 2, 9, 1, 5};
int *ptr = arr;
int min = *ptr;
for (int i = 1; i < 5; i++) {
if (*(ptr + i) < min) {
min = *(ptr + i);
}
}
printf("Smallest element: %d\n", min);
return 0;
}
Output:
Smallest element: 1
Visual Summary:
Based on the TU BIT syllabus for C Programming (BIT102), unit 7.
Discussion
Loading…