Relationship Between Pointer and Array in C
In C programming language, pointers and arrays are closely related. An array name acts like a pointer constant. The value of this pointer constant is the address of the first element. If we assign this value to a non-constant pointer of the same type, then we can access the elements of the array using this pointer.
For example, if we have an array named arr then arr and &arr[0] can be used interchangeably.
#include <stdio.h>
int main() {
// Declare an array
int arr[3] = { 5, 10, 15 };
// Access first element using index
printf("%d\n", arr[0]);
// Access first element using pointer
printf("%d\n", *arr);
return 0;
}
Output
5 5
Not only that, as the array elements are stored continuously, we can pointer arithmetic operations such as increment, decrement, addition, and subtraction of integers on pointer to move between array elements.
Traversing Array Using Pointer to First Element
#include <stdio.h>
int main() {
int arr[5] = { 1, 2, 3, 4, 5 };
int n = sizeof(arr) / sizeof(arr[0]);
// Defining the pointer to first element of array
int* ptr = &arr[0];
// Traversing array using pointer arithmetic
for (int i = 0; i < 5; i++)
printf("%d ",ptr[i]);
return 0;
}
Output
1 2 3 4 5
Passing Array to Function
There are multiple syntax to pass the arrays to function in C, but no matter what syntax we use, arrays are always passed to function using pointers. This phenomenon is called array decay.
Let's take a look at an example:
#include <stdio.h>
void f1(int arr[3]) {
printf("Size in f1: %lu bytes\n", sizeof(arr));
}
void f2(int arr[]) {
printf("Size in f2: %lu bytes\n", sizeof(arr));
}
void f3(int *arr) {
printf("Size in f3: %lu bytes\n", sizeof(arr));
}
int main() {
int arr[3] = { 1, 2, 3 };
printf("Size in main(): %lu bytes\n", sizeof(arr));
f1(arr);
f2(arr);
f3(arr);
return 0;
}
Output
Size in main(): 12 bytes Size in f1: 8 bytes Size in f2: 8 bytes Size in f3: 8 bytes
This concept is not limited to the one-dimensional array, we can refer to a multidimensional array element as well using pointers. Moreover, it gets more interesting in multidimensional arrays.
1. Pointers and 2D Arrays
In a two-dimensional array, we can access each element by using two subscripts, where the first subscript represents the row number, and the second subscript represents the column number. The elements of 2-D array can be accessed with the help of pointer notation also. Suppose arr is a 2-D array, we can access any element arr[i][j] of the array using the pointer expression:
*(*(arr + i) + j).
Now we'll see how this expression can be derived. Let's take a two-dimensional array arr[3][4]:
int arr[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} };
Since memory in a computer is organized linearly it is not possible to store the 2-D array in rows and columns. The concept of rows and columns is only theoretical. In reality, a 2-D array is stored in row-major order i.e rows are placed next to each other as shown:

Each row can be considered as a 1-D array, so a two-dimensional array can be considered as a collection of one-dimensional arrays that are placed one after another. We can say that here, arr is an array of 3 elements where each element is a 1-D array of 4 integers.
We know that the name of an array is a constant pointer that points to 0th 1-D array and contains address 5000. Since arr is a 'pointer to an array of 4 integers', according to pointer arithmetic the expression arr + 1 will represent the address 5016 and expression arr + 2 will represent address 5032.
So we can say that arr points to the 0th 1-D array, arr + 1 points to the 1st 1-D array and arr + 2 points to the 2nd 1-D array.

In general, we can write:
(arr + i ) points to ith 1-D array
Since, (arr + i) points to the ith row, dereferencing it gives the base address of the ith 1-D array, equivalent to arr[i]. To access a specific element in the ith row, we add j to (arr + i), such as *(arr + i) + j, which gives the address of the jth element. Dereferencing this expression provides the jth element of the ith row. Hence, we get: *(*(arr + i) + j).
Let's take a look at an example that uses pointers to traverse the 2D array:
#include <stdio.h>
int main() {
int arr[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8},
{9, 10, 11, 12} };
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++)
// using pointer notation for 2d array
printf("%d ", *(*(arr + i) + j));
printf("\n");
}
return 0;
}
Output
1 2 3 4 5 6 7 8 9 10 11 12
For passing 2d array to function using pointer, refer to this article - Pass a 2D array as a parameter
Pointers and 3D Arrays
In a three-dimensional array, we use three subscripts to access elements, where the first represents the depth, the second represents the row, and the third represents the column. Similar to 2-D arrays, elements in a 3-D array can be accessed using pointer notation. Suppose arr is a 3-D array, and we want to access the element arr[i][j][k], we can use the pointer expression:
*(*( *(arr + i) + j) + k)
We know the expression *(arr + i) is equivalent to arr[i] and the expression *(*(arr + i) + j) is equivalent arr[i][j]. So, we can say that arr[i] represents the base address of ith 2-D array and arr[i][j] represents the base address of the jth 1-D array.

Let's take a look at an example that uses pointers to traverse the 3D array:
#include <stdio.h>
int main() {
int arr[2][3][2] = {{{5, 10}, {6, 11}, {7, 12}},
{{20, 30}, {21, 31}, {22, 32}}};
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
for (int k = 0; k < 2; k++)
// Accessing using pointer notation
printf("%d ", *(*(*(arr + i) + j) + k));
printf("\n");
}
printf("\n");
}
return 0;
}
Output
5 10 6 11 7 12 20 30 21 31 22 32
The following figure shows how the 3-D array used in the above program is stored in memory.

To know how to pass 3D array to function - Pass a 3D Array to a Function