Pointers and Arrays in C
Pointers and Arrays in C language are closely linked. Using pointer arithmetic, it is possible to manipulate the elements of an array.
Similarly, in C, an array is a constant pointer that points to the initial element of the array. This lesson explains in detail how pointers and arrays work in C language.
Array Processing with Pointers
Pointer arithmetic allows us to manipulate the elements of an array by repeatedly incrementing or decrementing a pointer variable.
For example, suppose we want to sum all the elements contained in an array of n elements. We can define a variable sum to store the sum and a pointer variable p to store the address of the current element.
For this purpose we can use a for loop. We initialize the pointer variable to the address of the first element of the array and increment the pointer at each iteration of the loop. When the pointer reaches the address following the last element of the array, the loop terminates.
const int n = 10;
int a[n];
int sum = 0;
int *p;
/* Array initialization */
/* ... */
for (p = &a[0]; p < &a[n]; p++) {
sum += *p;
}
To understand better, let's look at what happens at each iteration of the for loop.
- At the beginning of the loop
pis initialized pointing to the first element of the array. The situation is shown in the figure:
- At the end of the first loop the variable
sum, which initially equals 0, is incremented bya[0]. The situation is shown in the figure:
- At the end of the second loop the pointer
ppoints to the next element, that isa[1]. Furthermore, the variablesumis incremented bya[1]. The situation is shown in the figure:
- At the end of the third loop the pointer
ppoints to the next element, that isa[2]. Furthermore, the variablesumis incremented bya[2]. The situation is shown in the figure:
The termination condition of the for loop is p < &a[n]. This condition is true as long as the pointer p does not reach the address of the element following the last element of the array. In other words, the loop terminates when the pointer p reaches the address of the element a[n].
At first sight it might seem illegal to take the address of a[n], that is of an element that does not exist since the array a goes from a[n]. It only checks that the address of p has reached the address of a[n]. In other words, the program does not attempt to perform the illegal operation of dereferencing the pointer p when it points to a[n].
The final situation is as follows:
This example shows how pointer arithmetic can be used to manipulate the elements of an array. We could have, alternatively, used an integer variable i to index the elements of the array.
Array Name as Pointer
Pointer arithmetic is one of the ways in which arrays and pointers are connected. However, there is another relationship.
Array Name as Pointer
In C language the name of an array represents the address of the first element of the array. This means that the name of an array can be used as a pointer.
This relationship simplifies pointer arithmetic and makes arrays and pointers much more versatile.
To understand in detail, suppose we have an array composed of 10 integers:
int a[10];
Based on what was said before, we can use the identifier a as a pointer that points to the element a[0]. We can therefore modify the element a[0] in this way:
/* Store the value 10 in element a[0] */
*a = 10;
We can also use pointer arithmetic to access the other elements of the array. For example we can modify the second element, a[1], in this way:
/* Store the value 20 in element a[1] */
*(a + 1) = 20;
In general, therefore, we can state that:
Arrays as Pointers: Equivalent Expressions
Since the name of an array is equivalent to a pointer to the first element, the following expressions are equivalent to each other:
-
The address of the i-th element of an array is equivalent to the address of the first element of the array incremented by
i:&a[i] == a + i -
Accessing the i-th element of an array is equivalent to dereferencing the pointer to the first element of the array incremented by
i:*(a + i) == a[i]
In other words, array indexing is an alternative form of pointer arithmetic.
The fact that the name of an array is equivalent to a pointer to the first element of the array allows us to simplify also the for loops that traverse arrays. For example, returning to the case of the sum of the elements of an array, we have the following for loop:
for (p = &a[0]; p < &a[n]; p++) {
sum += *p;
}
This for can be rewritten in a much simpler way like this:
for (p = a; p < a + n; p++) {
sum += *p;
}
The Name of an Array is a Constant Pointer
Although the name of an array can be used as a pointer, it is not possible to assign a new address to it. This is because the name of an array is a constant and cannot be modified.
For example, an expression like this causes a compiler error:
/* ERROR */
a = &a[1];
However it is always possible to use a support pointer to which to assign the address of the array and modify it subsequently:
int *p = a;
p = &a[1];
Array as Function Argument
When we pass an array as an argument to a function, the function parameter will always be treated as a pointer. This means that the function does not receive the array, but receives a pointer to the first element of the array.
For example, let's examine the following function that searches for and returns the maximum element of an array of integers:
int find_maximum(int a[], int n) {
int i;
int max = a[0];
for (i = 1; i < n; i++) {
if (a[i] > max) {
max = a[i];
}
}
return max;
}
Now, suppose we invoke the function in this way:
int a[10];
/* ... */
int max = find_maximum(a, 10);
What happens is that the function receives a pointer to the first element of the array a. In other words, the function receives a pointer to a[0]. The important consequence of this is that the array is not copied into the function.
Arrays as Function Parameters
An array passed as a function argument is always passed by reference. Therefore, when an array is passed to a function, the function receives a pointer to the first element of the array.
As a consequence the following two declarations are equivalent:
int find_maximum(int a[], int n);
int find_maximum(int *a, int n);
This has important consequences:
-
When an ordinary variable is passed to a function, its value is copied. Any change applied to the parameter has no effect on the original variable. Conversely, when an array is used as a function argument it is passed by reference and therefore is not protected from any modifications that the function makes. For example, we can write an
initializefunction that takes an array as input and initializes each single element to zero:void initialize(int a[], int n) { int i; for (i = 0; i < n; i++) { a[i] = 0; } }When you want to indicate that the function does not modify the content of an array, you can add the keyword
constto the array parameter. For example, thefind_maximumfunction does not modify the content of the array, so we can write:int find_maximum(const int a[], int n) { /* ... */ }This allows us to invoke the function in this way:
int a[10]; /* ... */ int max = find_maximum(a, 10);In this way, the compiler warns us if the function attempts to modify the content of the array.
-
Since an array is not passed by copy to a function, the time taken to pass it is independent of its size. Therefore the passage of an array is efficient from the performance point of view.
-
Instead of declaring a parameter as an array, we can always declare it as a pointer. For example, returning to the case of the
find_maximumfunction, we can rewrite the function in this way:int find_maximum(int *a, int n) { /* ... */ }The compiler treats the two declarations as identical.
Slicing
Since an array passed to a function is treated as a pointer, it is possible to use a particular technique to work only on a portion of an array. This technique is called slicing or partitioning.
To understand how it works, let's return to the example of the find_maximum function. Suppose we want to find the maximum element of a subset of the elements of an array. To do this it is not necessary to modify the function, we just need to invoke it in a different way.
For example, if a is an array composed of 10 elements but we want to find the maximum only among the elements of index 2, 3 and 4, we can invoke the function in this way:
int max = find_maximum(&a[2], 3);
In this way, the function receives a pointer to a[2] and the number of elements to consider is 3. The function will therefore work only on the elements a[2], a[3] and a[4].
Similarly we can find the maximum among the last 5 elements in this way:
int max = find_maximum(&a[5], 5);
The slicing technique works only if the partition comprises consecutive elements. It is not possible to partition the array by putting together non-contiguous elements.
Slicing or Partitioning of an Array
The slicing or partitioning of an array consists in invoking a function passing as an argument a pointer to the element of index i of an array and the number of elements to consider.
Slicing can be applied only to subsets of consecutive elements of an array.
Warning: when using the slicing technique it is necessary to ensure that the size of the partition does not exceed the size of the array. If this happens, there is a risk of accessing memory areas not assigned to the array.
Using a Pointer as an Array
Since the name of an array is a pointer, the opposite is also true, that is a pointer can be used as an array.
In fact, it is possible to use the indexing operator [] to access memory locations contiguous to those pointed to by the pointer itself.
For example, suppose we have a pointer p that points to an array of integers. We can access the elements of the array in this way:
int a[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int *p = a;
printf("%d\n", p[0]); // prints 0
printf("%d\n", p[1]); // prints 1
printf("%d\n", p[2]); // prints 2
To all intents and purposes, the compiler treats an expression of the type p[i] as if it were *(p + i). This functionality will be useful when we use dynamic arrays in the next lessons.
Pointers to Variable Length Arrays in C99
The C99 standard introduced variable length arrays which allow to declare arrays with a length determined at runtime.
Also in C99 it is possible to use pointers to point to them in the same way as any array.
For example:
int n = 10;
int a[n];
int *p = a;
In this case p is a pointer that points to the array a of length n. On p we can use pointer arithmetic and access the elements of the array in the same way as any array:
for (int i = 0; i < n; i++) {
p[i] = i;
}
Pointers to Variable Length Arrays in C99
In C99 it is possible to use pointers to point to variable length arrays in the same way as any array.
In Summary
In this lesson we have explored in detail the relationship that exists between arrays and pointers. In particular we have seen:
- How to access the elements of an array by exploiting pointer arithmetic;
- The name of an array represents to all intents and purposes a pointer to the first element of the array;
- When an array is passed to a function, it is passed by reference that is as a pointer;
- It is possible to use the slicing technique to work on a portion of an array;
- A pointer can be used as an array.
In the next lesson we will see the relationship that exists between pointers and multidimensional arrays.