Understanding Pointers in C: Arrays, Sorting, and Multi-Level Pointers
Array Name Interpretation
When working with pointers to access array elements, code like this is common:
int numbers[10] = {1,2,3,4,5,6,7,8,9,10};
int *ptr = &numbers[0];
Here, &numbers[0] obtains the address of the first element. However, the array name itself represents the address of the first element. This can be verified:
#include <stdio.h>
int main() {
int numbers[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%p\n", numbers);
printf("%p\n", &numbers[0]);
return 0;
}
Both lines output identical addresses, confirming that numbers equals &numbers[0].
But consider this:
#include <stdio.h>
int main() {
int numbers[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%zu\n", sizeof(numbers));
return 0;
}
If numbers were merely a pointer, sizeof(numbers) should yield 4 or 8 bytes (depending on platform). Instead, it outputs 40 (10 integers × 4 bytes each). This reveals two exceptions:
sizeof(array_name): When used alone insizeof, the array name represents the entire array, returning its total size in bytes.&array_name: Here, the array name represents the whole array, yielding the address of the entire array.
While &numbers[0], numbers, and &numbers may print the same value, their types differ:
#include <stdio.h>
int main() {
int numbers[10] = {1,2,3,4,5,6,7,8,9,10};
printf("&numbers[0] = %p\n", &numbers[0]);
printf("&numbers[0]+1 = %p\n", &numbers[0]+1);
printf("numbers = %p\n", numbers);
printf("numbers+1 = %p\n", numbers+1);
printf("&numbers = %p\n", &numbers);
printf("&numbers+1 = %p\n", &numbers+1);
return 0;
}
Output (example):
&numbers[0] = 0x7ffc88
&numbers[0]+1 = 0x7ffc8c // +4 bytes (int size)
numbers = 0x7ffc88
numbers+1 = 0x7ffc8c // +4 bytes
&numbers = 0x7ffc88
&numbers+1 = 0x7ffcb0 // +40 bytes (entire array size)
Pointer arithmetic respects the pointed-to type. Since &numbers is of type int (*)[10] (pointer to array of 10 ints), adding 1 skips 40 bytes.
Accessing Arrays via Pointers
With this understanding, arrays can be accessed using pointesr:
#include <stdio.h>
int main() {
int values[10] = {0};
int count = sizeof(values) / sizeof(values[0]);
int *ptr = values;
// Input
for (int idx = 0; idx < count; idx++) {
scanf("%d", ptr + idx); // Equivalent to values + idx
}
// Output
for (int idx = 0; idx < count; idx++) {
printf("%d ", *(ptr + idx));
}
return 0;
}
Since ptr and values are equivalent, ptr[idx] works identically to values[idx]. The expressions *(ptr+idx), *(idx+ptr), values[idx], ptr[idx], and even idx[ptr] are all equivalent. This is because array indexing arr[i] is compiled as *(arr + i).
The Nature of One-Dimensional Array Parameter Passing
Why can't we compute an array's size inside a function that receives it as a parameter?
#include <stdio.h>
void process_array(int arr[]) {
size_t size_inside = sizeof(arr) / sizeof(arr[0]);
printf("Inside function: %zu\n", size_inside);
}
int main() {
int data[10] = {1,2,3,4,5,6,7,8,9,10};
size_t size_outside = sizeof(data) / sizeof(data[0]);
printf("Outside function: %zu\n", size_outside);
process_array(data);
return 0;
}
Output (64-bit):
Outside function: 10
Inside function: 2 // 8 bytes (pointer) / 4 bytes (int) = 2
Array parameters decay to pointers. When data is passed to process_array, only the address of its first element is transmitted. Thus, sizeof(arr) inside the function yields the size of a pointer (8 bytes on x64), not the array.
Therefore, function signatures for array parameters should ideally use pointer syntax: void process_array(int *arr). Both int arr[] and int *arr are acceptable, but they're semantically identical—both are pointers.
Bubble Sort Implementation
Bubble sort operates by repeatedly comparing adjacent elements and swapping them if they're in the wrong order. Each pass places the next largest element in its correct position.
For an array of N elements:
- Pass 1: Perform N-1 comparisons, moving the largest element to the end.
- Pass 2: Perform N-2 comparisons (last element is sorted).
- Continue until only one element remains unsorted.
Implementation:
void bubble_sort(int *array, int element_count) {
for (int pass = 0; pass < element_count - 1; pass++) {
for (int pos = 0; pos < element_count - pass - 1; pos++) {
if (array[pos] > array[pos + 1]) {
// Swap
int temporary = array[pos];
array[pos] = array[pos + 1];
array[pos + 1] = temporary;
}
}
}
}
int main() {
int dataset[] = {2,3,1,4,10,7,5,6,8,9};
int total = sizeof(dataset) / sizeof(dataset[0]);
bubble_sort(dataset, total);
for (int i = 0; i < total; i++) {
printf("%d ", dataset[i]);
}
return 0;
}
Double Pointers
Pointer variables themselves occupy memory and have addressse. A double pointer (pointer-to-pointer) stores the address of a pointer variable.
#include <stdio.h>
int main() {
int value = 10;
int *single_ptr = &value;
int **double_ptr = &single_ptr;
printf("Address of value: %p\n", (void*)&value);
printf("Value of single_ptr: %p\n", (void*)single_ptr);
printf("Value of double_ptr: %p\n", (void*)double_ptr);
// Dereferencing
printf("*double_ptr == single_ptr: %s\n",
*double_ptr == single_ptr ? "true" : "false");
printf("**double_ptr == value: %s\n",
**double_ptr == value ? "true" : "false");
return 0;
}
double_ptr points to single_ptr. Dereferencing once (*double_ptr) yields single_ptr (which points to value). Dereferencing twice (**double_ptr) accesses value.
Arrays of Pointers
An array of pointers stores memory addresses as its elements.
int *pointer_array[5]; // Array of 5 integer pointers
Each element can point to an integer or an integer array.
Simulating a Two-Dimensional Array Using Pointer Arrays
Multiple one-dimensional arrays can be grouped via a pointer array to emulate a 2D structure:
int main() {
int row1[] = {1,2,3,4,5};
int row2[] = {2,3,4,5,6};
int row3[] = {3,4,5,6,7};
// Array names are addresses (int*), suitable for a pointer array
int *matrix[3] = {row1, row2, row3};
// Access like a 2D array
for (int r = 0; r < 3; r++) {
for (int c = 0; c < 5; c++) {
printf("%d ", matrix[r][c]);
}
printf("\n");
}
return 0;
}
matrix[r] retrieves a pointer to a row array. Adding [c] dereferences to access element c within that row. This structure provides similar syntax to a true 2D array while using separate 1D arrays in memory.