Pointers Deep Dive — Pointer Arithmetic, Arrays vs Pointers, Function Pointers
1505 words
8 min read
Visual companion
Python
Type and operator map
Python Week 1: the first filter for runtime behavior
View
Revision summary
What this note is really saying
Short form
# Pointers Deep Dive — Pointer Arithmetic, Arrays vs Pointers, Function Pointers ## 🎯 Learning Objectives - Declare and use pointers to various types - Perform pointer arithmetic correctly - Differentiate arrays and pointers - Use pointers with functions (call by reference) - Implement dynamic data structures using...

Pointers Deep Dive — Pointer Arithmetic, Arrays vs Pointers, Function Pointers
🎯 Learning Objectives
- Declare and use pointers to various types
- Perform pointer arithmetic correctly
- Differentiate arrays and pointers
- Use pointers with functions (call by reference)
- Implement dynamic data structures using pointers
1. Pointer Fundamentals
1.1 Intuition
A pointer is a variable that stores the memory address of another variable. Instead of holding a value directly (like
int x = 5), a pointer holds the address where the value lives. Think of it like a house address — you can find the house (value) by going to the address.1.2 Declaration and Initialization
cint x = 42; // x is an integer variable at some memory address int *p; // p is a pointer to an integer p = &x; // p now holds the address of x printf("Value of x: %d\n", x); // 42 printf("Address of x: %p\n", &x); // e.g., 0x7ffd5a1e4b1c printf("Value of p: %p\n", p); // same address as &x printf("Value at *p: %d\n", *p); // 42 (dereferencing)
1.3 Memory Layout
(Diagram)
1.4 Pointer Types and Sizes
| Declaration | Meaning | Size (64-bit) |
|---|---|---|
char *p | Pointer to char | 8 bytes |
int *p | Pointer to int | 8 bytes |
float *p | Pointer to float | 8 bytes |
double *p | Pointer to double | 8 bytes |
void *p | Generic pointer | 8 bytes |
All pointers are the same size (8 bytes on 64-bit systems) — they just differ in what they point to, which determines pointer arithmetic behavior.
2. Pointer Arithmetic
2.1 Rules
cint arr[5] = {10, 20, 30, 40, 50}; int *p = arr; // Points to arr[0] p + 1; // Points to arr[1] (address increases by sizeof(int) = 4 bytes) p + 2; // Points to arr[2] (address increases by 8 bytes) p - 1; // Points to arr[-1] (one position before start — dangerous!)
Addition/subtraction is scaled by the size of the pointed-to type:
cprintf("arr = %p\n", arr); // e.g., 0x1000 printf("arr+1 = %p\n", arr+1); // 0x1004 (not 0x1001!) printf("arr+2 = %p\n", arr+2); // 0x1008
2.2 Difference of Pointers
cint *p1 = &arr[0]; int *p2 = &arr[3]; ptrdiff_t diff = p2 - p1; // 3 (3 elements apart, NOT 12 bytes!)
The result is the number of elements between the two pointers, not bytes.
2.3 Pointer Comparison
cif (p1 < p2) { printf("p1 points to an earlier element\n"); }
Comparisons are valid only for pointers to the same array.
3. Arrays and Pointers
3.1 Array Name as Pointer
cint arr[5] = {10, 20, 30, 40, 50}; // arr is a constant pointer to arr[0] // arr + i is equivalent to &arr[i] // *(arr + i) is equivalent to arr[i] // Array subscript is pointer arithmetic: // arr[i] == *(arr + i) == *(i + arr) == i[arr] (yes, i[arr] works!) printf("3[arr] = %d\n", 3[arr]); // 40
3.2 Key Difference: sizeof
cint arr[5] = {10, 20, 30, 40, 50}; int *p = arr; printf("sizeof(arr) = %zu\n", sizeof(arr)); // 20 (5 × 4 bytes) printf("sizeof(p) = %zu\n", sizeof(p)); // 8 (pointer size) // Getting array size from pointer won't work! int size = sizeof(p) / sizeof(p[0]); // 8/4 = 2 (WRONG!)
3.3 Arrays Decay to Pointers
cvoid print_array(int *arr, int size) { // arr decays to pointer for (int i = 0; i < size; i++) { printf("%d ", arr[i]); // Works because arr[i] == *(arr + i) } } int main() { int data[5] = {1, 2, 3, 4, 5}; print_array(data, 5); // data decays to &data[0] // sizeof(data) in main() is 20, but sizeof(arr) in function is 8! }
4. Pointers and Functions
4.1 Call by Reference
cvoid swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } int main() { int x = 5, y = 10; swap(&x, &y); printf("x=%d, y=%d\n", x, y); // x=10, y=5 return 0; }
4.2 Pointer to Pointer (Double Pointer)
cint x = 42; int *p = &x; // p points to x int **pp = &p; // pp points to p printf("x = %d\n", x); // 42 printf("*p = %d\n", *p); // 42 printf("**pp = %d\n", **pp); // 42
(Diagram)
4.3 Function Pointers
c#include <stdio.h> int add(int a, int b) { return a + b; } int subtract(int a, int b) { return a - b; } int main() { // Declare a function pointer int (*operation)(int, int); operation = add; printf("add(5, 3) = %d\n", operation(5, 3)); // 8 operation = subtract; printf("subtract(5, 3) = %d\n", operation(5, 3)); // 2 return 0; }
5. Dynamic Memory Allocation
5.1 malloc, calloc, realloc, free
c// malloc: allocate uninitialized memory int *arr = (int*)malloc(5 * sizeof(int)); // calloc: allocate zero-initialized memory int *arr2 = (int*)calloc(5, sizeof(int)); // realloc: resize existing allocation arr = (int*)realloc(arr, 10 * sizeof(int)); // free: release memory free(arr); free(arr2);
5.2 Common Dynamic Memory Patterns
c// Create a dynamic array int *create_array(int size) { int *arr = (int*)malloc(size * sizeof(int)); if (arr == NULL) { fprintf(stderr, "Memory allocation failed\n"); exit(1); } return arr; } // 2D array using pointer-to-pointer int **matrix = (int**)malloc(rows * sizeof(int*)); for (int i = 0; i < rows; i++) { matrix[i] = (int*)malloc(cols * sizeof(int)); }
6. Common Pitfalls
Pitfall 1: Dereferencing NULL or uninitialized pointers
cint *p; *p = 42; // SEGFAULT! p is uninitialized int *q = NULL; *q = 42; // SEGFAULT! dereferencing NULL
Always initialize pointers. Set to NULL if not immediately assigned.
Pitfall 2: Buffer overflow
cint arr[5]; for (int i = 0; i <= 5; i++) { // Off-by-one! arr[i] = i * 10; // arr[5] writes beyond allocated memory }
Pitfall 3: Memory leaks
cvoid leak() { int *p = (int*)malloc(100 * sizeof(int)); // Never call free(p)! return; }
Always free dynamically allocated memory. Use tools like valgrind to detect leaks.
Pitfall 4: Dangling pointers
cint *p = (int*)malloc(sizeof(int)); free(p); *p = 42; // Dangling pointer! p still points to freed memory
Set freed pointers to NULL to catch use-after-free bugs.
7. 📐 Key Formulas / Concepts
| Concept | Expression | Meaning |
|---|---|---|
| Address-of | &x | Memory address of variable x |
| Dereference | *p | Value at address p |
| Array subscript | arr[i] | *(arr + i) — pointer arithmetic |
| Pointer difference | p2 - p1 | Number of elements between pointers |
| Arrow operator | p->member | (*p).member — access struct member via pointer |
8. 📝 Practice Questions
Q1: What is the output?int arr[] = {10, 20, 30, 40}; int *p = arr; printf("%d", *(p+2) + 3);Answer: 33.*(p+2)=arr[2]= 30. 30 + 3 = 33. Q2: Explain whysizeof(arr)in a function parameter gives the wrong result.Answer: When an array is passed to a function, it "decays" to a pointer to its first element. In the function parameter,int arr[]is equivalent toint *arr, andsizeof(arr)returns the size of a pointer (8 bytes on 64-bit), not the array size. Q3: Write a function that takes a string and reverses it in-place using pointers.cvoid reverse(char *str) { char *start = str; char *end = str; while (*end) end++; // Find end of string end--; // Point to last character while (start < end) { char temp = *start; *start = *end; *end = temp; start++; end--; } }Q4: What is a memory leak and how can you prevent it?Answer: A memory leak occurs when dynamically allocated memory is not freed after use. Prevent by: (1) always pairing malloc with free, (2) using tools like valgrind, (3) following RAII-like patterns (allocate in constructor, free in destructor), (4) setting freed pointers to NULL. Q5: What is the difference betweenchar *s = "hello"andchar s[] = "hello"?Answer:char *s = "hello"creates a pointer to a string literal (read-only, stored in .rodata).char s[] = "hello"creates a mutable array (stored on stack, can be modified). Modifying a string literal via*sis undefined behavior (usually crashes). Q6: Write a function that returns a pointer to the maximum element in an array.cint *max_element(int *arr, int size) { if (size == 0) return NULL; int *max = arr; for (int i = 1; i < size; i++) { if (arr[i] > *max) max = &arr[i]; } return max; }
9. 🔗 Cross-References
- Week 5 - Dynamic Memory: malloc, calloc, free deep dive
- Week 6 - Structures: Pointers to structs, linked lists
- BSCS4022 (OS): System calls, process memory layout
- BSCS3031 (CSD): Address translation, memory hierarchy Join Discord PreviousControl Flow & ArraysNextFunctions