A pointer to an array is a variable that stores the memory address of an entire array.
While an array's name often decays into a pointer to its first element, a pointer to an array treats the array as a single, indivisible object. This subtle but crucial distinction has significant implications for type safety, pointer arithmetic, and working with multi-dimensional arrays.
Declaration: The crucial parentheses
The syntax for declaring a pointer to an array is where most confusion arises, as it requires parentheses to override operator precedence.
// Correct: `ptr_to_arr` is a pointer to an array of 5 integers.
int (*ptr_to_arr)[5];
// Incorrect: `arr_of_ptr` is an array of 5 integer pointers.
int *arr_of_ptr[5];
Use code with caution.
In the correct declaration, the parentheses (*ptr_to_arr) force the compiler to first treat ptr_to_arr as a pointer before applying the array subscript [5]. This means the pointer's type is "pointer to an array of 5 integers." Without the parentheses, the higher precedence of the square brackets [] creates an "array of 5 integer pointers" instead.
Initialization
To initialize a pointer to an array, you must take the address of the entire array using the & operator.
int arr[5] = {10, 20, 30, 40, 50};
int (*ptr_to_arr)[5] = &arr;
Use code with caution.
It is a common mistake to simply assign the array name, arr, to the pointer. This will cause a compiler warning or error because the types do not match. The array name arr decays into a pointer of type int*, which is a pointer to an integer. However, ptr_to_arr has the type int (*)[5], which is a pointer to an array of 5 integers.
Pointer arithmetic: A fundamental difference
The true power and purpose of a pointer to an array become apparent in pointer arithmetic, where the base type and size govern how the address is incremented.
- Pointer to an element: Incrementing a pointer to the first element (
int* p = arr;) moves it forward by the size of a single element (e.g., 4 bytes for anint). - Pointer to an array: Incrementing a pointer to an array (
int (*ptr_to_arr)[5] = &arr;) moves it forward by the size of the entire array (e.g., 5 elements * 4 bytes/element = 20 bytes).
This behavior is clearly demonstrated with sizeof and pointer arithmetic:
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int* ptr_to_element = arr;
int (*ptr_to_arr)[5] = &arr;
// The addresses stored are identical
printf("Address of array: %p\n", arr);
printf("Address of element pointer: %p\n", ptr_to_element);
printf("Address of array pointer: %p\n\n", ptr_to_arr);
// Size of the pointer variables themselves (typically 8 bytes on 64-bit systems)
printf("Size of element pointer: %zu\n", sizeof(ptr_to_element));
printf("Size of array pointer: %zu\n\n", sizeof(ptr_to_arr));
// Size of the data they point to
printf("Size of dereferenced element pointer (*p): %zu\n", sizeof(*ptr_to_element)); // 4 bytes (size of int)
printf("Size of dereferenced array pointer (*ptr): %zu\n\n", sizeof(*ptr_to_arr)); // 20 bytes (size of int[5])
// Pointer arithmetic
printf("Incrementing element pointer: %p\n", ptr_to_element + 1); // Increments by 4 bytes
printf("Incrementing array pointer: %p\n", ptr_to_arr + 1); // Increments by 20 bytes
return 0;
}
Use code with caution.
Dereferencing
When you dereference a pointer to an array, you get back the array itself. From there, you can use the subscript operator [] to access its elements.
int (*ptr_to_arr)[5] = &arr;
// Accessing the third element (index 2)
int value = (*ptr_to_arr)[2]; // Parentheses are crucial due to precedence
Use code with caution.
The expression *ptr_to_arr evaluates to the array arr. However, the parentheses are essential because the subscript operator [] has higher precedence than the dereference operator *. Without them, *ptr_to_arr[2] would first be interpreted as *(ptr_to_arr[2]), which would attempt to access an element of the pointer and then dereference it, leading to a compile error.
Key applications
- Preserving array size in function calls: When an array is passed to a function, it "decays" into a simple pointer to its first element, and its size information is lost. By passing a pointer to the entire array (
int (*ptr)[5]), the function can preserve and operate on the array's full type, including its size. - Working with multi-dimensional arrays: A two-dimensional array, such as
int matrix[2][3], is an array of arrays. A pointer to this structure can be declared asint (*ptr)[3], meaning it points to an array of 3 integers. Incrementing this pointer moves it to the next row (the next array of 3 integers). This makes traversing multi-dimensional arrays with pointers straightforward and type-safe. - Dynamic allocation of multi-dimensional arrays: While a two-dimensional array is a contiguous block of memory, you can use pointers to arrays to simulate multi-dimensional arrays on the heap.
int (*dynamic_2d_array)[10] = malloc(5 * sizeof(int[10]));
// You can now access elements like a 2D array:
dynamic_2d_array[2][5] = 99;
// Remember to free the memory
free(dynamic_2d_array);
Use code with caution.