Relationship Between Arrays and Pointers
Array-to-pointer decay, contiguous layout, and safe traversal via pointer arithmetic.
In C++, arrays and pointers are closely related. This is because arrays are stored sequentially in memory, and the name of an array actually represents the address of its first element. Therefore, it is possible to traverse an array using pointer arithmetic. In this article, we will learn about the memory structure of arrays, their relationship with pointers, and practical usage examples.
1. Arrays and Memory Layout
When an array is defined in C++, all its elements are stored consecutively in memory. For example:
#include <iostream>
using namespace std;
int main() {
int numbers[3] = {10, 20, 30};
cout << "Address of 1st element: " << &numbers[0] << endl;
cout << "Address of 2nd element: " << &numbers[1] << endl;
cout << "Address of 3rd element: " << &numbers[2] << endl;
return 0;
}
The output addresses will appear sequentially:
Address of 1st element: 0x61ff0c
Address of 2nd element: 0x61ff10
Address of 3rd element: 0x61ff14
Since each int occupies 4 bytes, the addresses increase by 4.
2. The Array Name Is a Pointer
The name of the array (numbers) actually represents the address of its first element.
In other words, numbers and &numbers[0] refer to the same address.
int numbers[3] = {10, 20, 30};
cout << numbers << endl; // address of first element
cout << &numbers[0] << endl; // same address
Therefore, you can traverse an array using pointers.
3. Accessing Array Elements with Pointers
If a pointer points to the first element of an array, pointer arithmetic can be used to access the other elements.
#include <iostream>
using namespace std;
int main() {
int numbers[4] = {5, 10, 15, 20};
int *p = numbers; // p = &numbers[0]
cout << *p << endl; // 5
cout << *(p + 1) << endl; // 10
cout << *(p + 2) << endl; // 15
cout << *(p + 3) << endl; // 20
}
The expression *(p + i) is equivalent to numbers[i].
This forms the foundation of pointer arithmetic.
4. Pointer Arithmetic
Pointers automatically move by the correct amount in memory based on their type. For example:
int array[3] = {1, 2, 3};
int *ptr = array;
cout << ptr << endl; // address of first element
ptr++;
cout << ptr << endl; // address of the next element
The expression ptr++ actually advances the address by 4 bytes (int = 4 bytes).
This is automatically calculated based on the pointer’s type.
For example, double advances by 8 bytes, while char advances by 1 byte.
5. Iterating Through an Array Using a Pointer
You can loop through an array using a pointer.
int numbers[] = {2, 4, 6, 8, 10};
int *p = numbers;
for (int i = 0; i < 5; i++) {
cout << *(p + i) << " ";
}
Alternatively, the pointer can be incremented directly:
for (int *ptr = numbers; ptr < numbers + 5; ptr++) {
cout << *ptr << " ";
}
Both methods produce the same result:
2 4 6 8 10
6. Array Parameters in Functions
When an array is passed to a function, what is actually passed is the address of the array. Therefore, the function can modify the array directly.
#include <iostream>
using namespace std;
void Fill(int *array, int length) {
for (int i = 0; i < length; i++)
array[i] = (i + 1) * 5;
}
void Print(const int *array, int length) {
for (int i = 0; i < length; i++)
cout << array[i] << " ";
}
int main() {
int numbers[5];
Fill(numbers, 5);
Print(numbers, 5);
}
Since the Fill function modifies the array directly in memory,
the values in the main() function are updated.
7. Multidimensional Arrays and Pointers
Multidimensional arrays are also stored sequentially in memory. However, pointer arithmetic requires a bit more care.
#include <iostream>
using namespace std;
int main() {
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
cout << *(*(matrix + 0) + 2) << endl; // 3
cout << *(*(matrix + 1) + 0) << endl; // 4
}
Here, matrix → is the address of the first row.
*(matrix + 1) → moves to the second row,
and *(*(matrix + 1) + 0) → accesses the first element of that row.
8. Pointers and Dynamic Arrays
Unlike fixed-size arrays, dynamic arrays are created using the new keyword.
These arrays are stored in heap memory, and their size can be determined at runtime.
int *array = new int[5];
for (int i = 0; i < 5; i++)
array[i] = (i + 1) * 10;
for (int i = 0; i < 5; i++)
cout << array[i] << " ";
delete[] array; // Free memory
Dynamic arrays require manual memory management.
Memory allocated with new must always be released with delete[] after use.
9. Reading Arrays with const Pointers
If you don't want a function to modify the array, the pointer parameter can be defined as const.
void Print(const int *array, int length) {
for (int i = 0; i < length; i++)
cout << array[i] << " ";
}
This approach preserves data integrity and prevents the array from being accidentally modified.
10. TL;DR
- The array name (
numbers) → is the address of the first element (&numbers[0]). *(p + i)↔array[i]are equivalent expressions.- Pointer arithmetic automatically advances the address based on the data type size.
- Arrays are passed to functions by address, not by value (no copying).
- Multidimensional arrays can be accessed through pointer chains (
*(*(matrix + i) + j)). - Dynamic arrays should be created with
newand freed withdelete[]. - All examples can be run in Visual Studio 2022 or GCC 11+ compilers.