Pointers and Arrays in C: Memory Safety and Multi-dimensional Access
1. Segmentation Faults
When executing C programs, ancountering "segmentation fault" errors can be frustrating due to their vague nature. Here are the common causes and patterns leading to these crashes.
1.1 Wild Pointers
Uninitialized Pointers: Declaring a pointer without assigning a valid address results in undefined behavior when dereferenced.
int *uninitialized;
scanf("%d", uninitialized); // Dangerous: dereferencing random memory address
Dangling Pointers: After deallocating memory with free(), failing to set the pointer to NULL leaves a "dangling" reference that appears valid but points to reclaimed memory.
int *data_block = (int *)malloc(sizeof(int) * 20);
free(data_block);
// data_block now dangling - Solution: data_block = NULL;
1.2 Memory Leaks and Invalid Access
int *buffer = (int *)malloc(100 * sizeof(int));
buffer = (int *)malloc(200 * sizeof(int)); // Memory leak: first allocation lost
When reassigning a pointer to new memory without freeing the previous allocation, the original memory becomes unreachable. This "garbage memory" remains allocated but inaccessible, wasting system resources.
2. Double Pointers
A double pointer (pointer-to-pointer) stores the memory address of another pointer variable rather than a data variable.
- Single pointer: Stores the address of a variable
- Double pointer: Stores the address of a pointer
- Syntax:
storage_type data_type **pointer_variable;
Example declaration: int **meta_ptr;
3. Pointers and Arrays
Array elements can be accessed through two fundamental approaches:
- Direct Access: Using the array name (base address) to index elements
- Indirect Access: Using a pointer variable that holds the array's address
Key Concept: Array names represent constant base addresses, while pointers are variables that can store and modify addresses.
3.1 Pointers and One-Dimensional Arrays
Direct Access Example: int values[3] = {10, 20, 30};
| Address | Equivalent | Element | Value Access | |
|---|---|---|---|---|
values |
&values[0] |
10 | values[0] |
*values |
values+1 |
&values[1] |
20 | values[1] |
*(values+1) |
values+2 |
&values[2] |
30 | values[2] |
*(values+2) |
Indirect Access: int *cursor = values;
| Address | Equivalent | Element | Value Access | |
|---|---|---|---|---|
cursor |
&cursor[0] |
10 | cursor[0] |
*cursor |
cursor+1 |
&cursor[1] |
20 | cursor[1] |
*(cursor+1) |
cursor+2 |
&cursor[2] |
30 | cursor[2] |
*(cursor+2) |
Critical Distinction: While values and cursor both represent addresses, values is an address constant (cannot use values++), whereas cursor is a modifiable variable (can use cursor++).
Access Patterns for Element at Index i:
- Values:
values[i]or*(values+i) - Addresses:
&values[i]orvalues+i - Via Pointer:
cursor[i]or*(cursor+i) - Pointer Addresses:
&cursor[i]orcursor+i
3.2 Pointers and Two-Dimensional Arrays
In two-dimensional arrays, pointer arithmetic operates on rows. The array name represents a pointer to the first row, not the first element. To access individual columns, apply the dereference operator * to "downgrade" from a row pointer to an element pointer.
matrix: Address of first row (type:int (*)[cols])*matrix: Address of first element, first row (type:int *)(*matrix)+1: Address of first row, second column
Direct Access: int matrix[2][2] = {1, 2, 3, 4};
| Address Expression | Equivalent Forms | Element | Value Access | Alternative |
|---|---|---|---|---|
matrix[0] |
matrix / *matrix |
&matrix[0][0] |
1 | matrix[0][0] / **matrix / *matrix[0] |
matrix[0]+1 |
*matrix+1 |
&matrix[0][1] |
2 | matrix[0][1] / *(*matrix+1) / *(matrix[0]+1) |
matrix[1] |
matrix+1 / *(matrix+1) |
&matrix[1][0] |
3 | matrix[1][0] / *(*(matrix+1)) / *matrix[1] |
matrix[1]+1 |
*(matrix+1)+1 |
&matrix[1][1] |
4 | matrix[1][1] / *(*(matrix+1)+1) / *(matrix[1]+1) |
Array Pointers (Row Pointers)
An array pointer is a pointer that points to an entire array (row) rather then a single element.
- Definition: Pointer to array (row pointer)
- Syntax:
storage_type data_type (*pointer)[column_count];
int grid[2][3] = {1, 2, 3, 4, 5, 6};
int (*row_ptr)[3] = grid; // Points to array of 3 integers
Both row_ptr and grid behave identically. Incrementing either by 1 advances the address by one full row (3 integer units in this case).
Indirect Access Table (using row_ptr):
| Address Expression | Equivalant Forms | Element | Value Access | Alternative |
|---|---|---|---|---|
row_ptr[0] |
row_ptr / *row_ptr |
&row_ptr[0][0] |
1 | row_ptr[0][0] / **row_ptr / *row_ptr[0] |
row_ptr[0]+1 |
*row_ptr+1 |
&row_ptr[0][1] |
2 | row_ptr[0][1] / *(*row_ptr+1) / *(row_ptr[0]+1) |
row_ptr[1] |
row_ptr+1 / *(row_ptr+1) |
&row_ptr[1][0] |
3 | row_ptr[1][0] / *(*(row_ptr+1)) / *row_ptr[1] |
row_ptr[1]+1 |
*(row_ptr+1)+1 |
&row_ptr[1][1] |
4 | row_ptr[1][1] / *(*(row_ptr+1)+1) / *(row_ptr[1]+1) |
Accessing matrix[i][j]:
- Direct:
matrix[i][j]/*(*(matrix+i)+j)/*(matrix[i]+j) - Indirect:
row_ptr[i][j]/*(*(row_ptr+i)+j)/*(row_ptr[i]+j)
Address of matrix[i][j]:
- Direct:
&matrix[i][j]/*(matrix+i)+j/matrix[i]+j - Indirect:
&row_ptr[i][j]/*(row_ptr+i)+j/row_ptr[i]+j
3.3 Pointer Arrays
A pointer array is an array whose elements are pointers rather than data values.
- Definition: Array containing pointer elements
- Syntax:
storage_type data_type *array_name[element_count];
Example: int *ptr_array[3];
Relationship with Double Pointers:
The array name of a pointer array decays to a double pointer. Consider this hierarchy:
double_ptr -> ptr_array -> target_value
Equivalence Relations:
double_ptr = &ptr_array;*double_ptr = ptr_array;ptr_array = &target_value;*ptr_array = target_value;
Substituting (3) into (2): *double_ptr = &target_value
Therefore, dereferencing twice reaches the value: **double_ptr = target_value
Practice Exercise
Problem: Given two sorted character arrays buffer1[10] and buffer2[10], implement a pointer-based solution to merge them into a single sorted output.
Example:
- Input:
buffer1 = "acdgjmno",buffer2 = "befhil" - Output:
"abcdefghijlmno"
#include <stdio.h>
#include <string.h>
int main(void) {
char buffer1[20], buffer2[20];
char *iter1, *iter2;
// Read input strings
fgets(buffer1, sizeof(buffer1), stdin);
fgets(buffer2, sizeof(buffer2), stdin);
// Remove trailing newlines
buffer1[strcspn(buffer1, "\n")] = '\0';
buffer2[strcspn(buffer2, "\n")] = '\0';
iter1 = buffer1;
iter2 = buffer2;
// Merge sorted arrays using two-pointer technique
while (*iter1 != '\0' && *iter2 != '\0') {
if (*iter1 < *iter2) {
putchar(*iter1);
iter1++;
} else {
putchar(*iter2);
iter2++;
}
}
// Append remaining elements from either buffer
if (*iter1 != '\0') {
printf("%s", iter1);
}
if (*iter2 != '\0') {
printf("%s", iter2);
}
putchar('\n');
return 0;
}