C Programming: Pointers
Introduction
A pointer is a variable whose value is the address of another variable. Hence we think of them as pointing to another variable. Knowing the specific address of a variable is typically not what we are interested in. Instead, we can use the pointer to access the value of the variable whose address is stored in the pointer variable.
Pointer variables have their own type, which should match the type of variable that they point to. For example, a pointer that contains the address of an int
will be of type pointer-to-int
, a pointer that contains the address of a double
will be of type pointer-to-double
, and so forth. This becomes relevant when we declare pointer variables, which must be declared just as variables of other types of variables are declared.
For all of our examples, we assume we are on a machine in which a variable of type char
is one byte, a variable of type int
is four bytes, a variable of type double
is eight bytes, and an address is four bytes.
Pointers to Variables
In order to get the address of a variable, we precede it with an ampersand (i.e., &), also known as the address operator.
To declare a pointer variable, we precede the variable name by an asterisk (i.e., *). For example, the following lines of code declare and initialize a variable x
of type int
, declare a variable ptr
of type pointer-to-int
(or int-star
), and assign the address of x
to ptr
:
int x = 5;
int* ptr;
ptr = &x;
Just as other types of variables can be declared and initialized in the same line of code, we can declare and initialize pointer variables. Therefore, I could have written the example above like this:
int x = 5;
int* ptr = &x;
In the previous examples, when I declared the pointer variable the type was int*
and the name of the variable was ptr
.
Since the value of ptr
is the address of the variable x
, when I use the name ptr
I get the address. For example, compiling and running the program
#include <stdio.h>
int main(void) {
int x = 5;
int* ptr = &x;
printf("the value of ptr is %p\n", ptr);
}
produces
the value of ptr is 0xbf893f10
which is an address in hexadecimal (you should expect to get a different address).
Notice the format specifier we used in the printf()
statement in this example is %p
, which indicates that we are printing the content of a pointer.
As I stated in the introduction, we typically are not interested in the specific address in memory where a value is stored and, in fact, this value is determined at run time anyway. In most cases we would like to use the pointer to access the value at the address stored in the pointer. To do this, we precede the pointer variable with an asterisk, also known as the indirection operator. By doing so, we can use the pointer in place of the variable it points to:
#include <stdio.h>
int main(void) {
int x = 5;
int y = 20;
int* ptr = &x;
printf("x is %d, *ptr is %d\n", x, *ptr);
y += *ptr; /* line 11 */
printf("y is now %d\n", y);
(*ptr)++; /* line 14 */
printf("x is now %d, *ptr is now %d\n", x, *ptr);
}
produces
x is 5, *ptr is 5
y is now 25
x is now 6, *ptr is now 6
On line 11 of the above program, I am using the pointer to add the value of x
to the value of y
. On line 14 I use the pointer to increment the value of x
. Notice that when doing so, I have enclosed *ptr
in parentheses.
The indirection operator and the increment operator (i.e., ++) have the same level of precedence, but these operators are evaluated from right to left. Therefore, the statement
(*ptr)++;
increments the value of the variable pointed to by ptr
, which is what I wanted here, while
*ptr++;
would increment the content of ptr
(i.e., change the value of the address it points to) and then get the value at the new address. This is legal, and useful in some instances, but not what I wanted in this case.
Pointers as Function Parameters
It is common in C to call a function that returns a value, which we store in a local variable. For example,
y = someFunction(x);
saves the value returned by someFunction
in the variable y
. C does not allow a function to return multiple values. The way around this is to pass the addresses of local variables to the function, which then manipulates the values of the local variables via pointers as shown in this example:
#include <stdio.h>
void swap(int*, int*);
int main(void) {
int x = 5;
int y = 8;
swap(&x, &y);
printf("x is %d, y is %d\n", x, y);
}
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
produces
x is 8, y is 5
Notice that in the function call, we use the address operator to get the addresses of variables x
and y
.
In our function definition, we declare two variables of type pointer-to-int
, so we are essentially doing this:
int* a = &x;
int* b = &y;
We then use *a
and *b
in the function just as we would had we performed the swap in main()
instead.
Pointers to One-Dimensional Arrays
We can have pointers to arrays. To create a pointer to an array, we can do something like this:
int data[4] = {1, 2, 3, 4};
int* ptr = data;
Notice in this case that I did not precede the name of the array, data
, by an ampersand. This is because using the name of the array gives us the address of the beginning of the array, which we can see in this example:
#include <stdio.h>
int main(void) {
int data[4] = {1, 2, 3, 4};
int* ptr = data;
printf("the location of data is %p\n", data);
printf("the value of ptr is %p\n", ptr);
}
produces
the location of data is 0xbfcc3c14
the value of ptr is 0xbfcc3c14
When I declared a pointer to a variable of type int
, I could use or change the value of that variable by prefixing the pointer with an asterisk.
I can do the same with a pointer to an array, as shown in
#include <stdio.h>
int main(void) {
int data[4] = {8, 9, 10, 11};
int* ptr = data;
int i;
printf("data begins at %p\n\n", data);
printf("Element Value Address\n");
printf("-----------------------\n");
for(i = 0; i < 4; i++) {
printf(" %d %2d %p\n", i, *ptr, ptr);
ptr++;
}
}
produces
data begins at 0xbfd03700
Element Value Address
-----------------------
0 8 0xbfd03700
1 9 0xbfd03704
2 10 0xbfd03708
3 11 0xbfd0370c
To understand how this program works, we need to discuss two other things: how array elements are stored in memory and pointer arithmetic. When an array is declared, memory is allocated for it such that all of the variables will be in contiguous memory locations. That is, they are right next to each other in memory and, as was demonstrated by this program, we can just increment the address stored in our pointer so that it points to the address of the next element.
We can see from the output of the program that the memory addresses of the elements next to each other differ by 4, representing the four bytes that each int
occupies in memory, yet in the program the pointer value was incremented using the increment operator. When we use this to increment the value of an int
, it adds 1 to the int
so how does this allow us to move from one address to the next? When we use the increment operator on a pointer, it uses pointer arithmetic to determine how much to add to the address.
Pointer arithmetic uses the size of the type of variable that the pointer point to to determine how much to change the address.
In this example the variables stored in the array are int
s, each of which takes four bytes. By incrementing the pointer by 1, we are really adding the size of one int
to the address stored in the pointer.
We’re not limited to incrementing an address by just 1; we can increment by other amounts and even decrement it as well. In the example below, the value of the pointer is initially set to the address of the beginning of the array, which is also the address of the first element. By incrementing the address by the size of three int
s, we get the address of the last element. By decrementing this address by the size of two int
s, we get the address of the second element.
#include <stdio.h>
int main(void) {
int data[4] = {8, 9, 10, 11};
int* ptr = data;
printf("address of %d is %p\n", *ptr, ptr);
ptr += 3;
printf("address of %d is %p\n", *ptr, ptr);
ptr = ptr - 2;
printf("address of %d is %p\n", *ptr, ptr);
}
produces
address of 8 is 0xbfd67cb4
address of 11 is 0xbfd67cc0
address of 9 is 0xbfd67cb8
While there is nothing wrong with creating a pointer to the array and using it to access the array elements, we can use the fact that the array name evaluates to the address of the beginning of the array.
However, since an array has multiple elements, we need to include some additional information to reference specific elements of the array. For this we need to include an offset from the beginning of the array (i.e., the base) to the specific element.
To get the address of any element of the array, we just add the number of elements from base to the element in question:
array_name + offset
To get the value at this address, we use the indirection operator:
*(array_name + offset)
#include <stdio.h>
int main(void) {
int data[4] = {8, 9, 10, 11};
printf("address of %2d is %p\n", *(data + 0), data + 0);
printf("address of %2d is %p\n", *(data + 3), data + 3);
}
produces
address of 8 is 0xbf90cbd8
address of 11 is 0xbf90cbe4
In the preceding example, when the offset was zero I could have left it out and just used data
to get the address of the first element of the array and *data
to get its value.
How does this approach, referred to as pointer notation, compare to using subscript notation to access array elements? The offset and the subscript are both equal to the index of the array element in this example:
#include <stdio.h>
int main(void) {
int data[4] = {8, 9, 10, 11};
int i;
for (i = 0; i < 4; i++)
printf("%2d, %2d\n", *(data + i), data[i]);
}
produces
8, 8
9, 9
10, 10
11, 11
Pointers to Two-Dimensional Arrays
Using pointer notation to access elements of a two-dimensional (2D) array is much like accessing a one-dimensional array. The difference is that now we must account for the rows as well as the columns when considering offsets.
The form for getting the address of a specific element of the array is
*(array_name + row) + col
To get the value at this address, we use
*(*(array_name + row) + col)
This is equivalent to array_name[row][col]
if we are using subscript notation.
Let’s break down the form for using pointer notation to access the elements of a 2D array.
It is helpful to remember that a 2D array is an array of arrays. If the dimensions of the array are R * C, then there are R rows, each of which is an array. Each of these R arrays has C elements of the type the array was declared to store, for example int
.
The part of the form
*(array_name + row)
produces the address of a row. For example, if row = 2
and the array type is int
, then *(array_name + 2)
is the address of the beginning of the 3rd row.
Pointer arithmetic does the work for us of adding the size of two rows
of int
s to the base address.
If we then include the offset for the column of the array element,
*(array_name + row) + col
we get the address of the specific element of the array. To get the value of this element, we add another indirection operator:
*(*(array_name + row) + col)
If either the row or column values are zero, we can leave them out but must be careful that we keep whatever parentheses are necessary to maintain the appropriate relationships between the indirection operators and the offsets. For example,
*(*(data + 2) + 0) <==> **(data + 2) <==> data[2][0]
*(*(data + 0) + 3) <==> *(*data + 3) <==> data[0][3]
and
If both the row and column values are zero, we can leave both out to get
**data
In this example I work with the array name instead of creating a pointer to the array:
#include <stdio.h>
int main(void) {
int data[2][3] = { { 8, 9, 10},
{11, 12, 13} };
printf("%p\n", *(data + 1) );
printf("%2d\n", **(data + 1) );
printf("%p\n", (*(data + 1) + 2) );
printf("%2d\n", *(*(data + 1) + 2) );
}
produces
0xbf96cf74
11
0xbf96cf7c
13
How does the following example work?
#include <stdio.h>
int main(void) {
int i;
int data[4][3]= { { 1, 2, 3},
{ 4, 5, 6},
{ 7, 8, 9},
{10, 11, 12} };
/* (*rowptr)[3] is a pointer to the first row
(an array) of data.
*rowptr is the address of one of the rows.
to get the value of a
specific element on the row, we prefix
with * again */
int (*rowptr)[3] = data;
/* use offsets to the base address to
print the second column */
for(i = 0; i < 4; i++)
printf("%p %2d\n", *(rowptr + i) + 1,
*(*(rowptr + i) + 1) );
printf("\n");
for(i = 0; i < 4; i++) {
printf("%p %2d\n", *rowptr + 1,
*(*rowptr + 1) );
rowptr++;
}
}
produces
0xbfc95e6c 2
0xbfc95e78 5
0xbfc95e84 8
0xbfc95e90 11
0xbfc95e6c 2
0xbfc95e78 5
0xbfc95e84 8
0xbfc95e90 11
In order to understand this example, we need to remember that a 2D array is an array of arrays. When we use just the name, data
, we are referencing the first element of the array which is another array (we can think of this as the first of the four arrays, or rows, inside data
). Therefore, in order to create a pointer to a row, we use
int (*rowptr)[3] = data;
which says that our variable, rowptr
, is a pointer to an array that is the size of three int
s (the parentheses make this a pointer to an array instead of an array of pointers). To use this pointer, in the first for
loop the row offset changes while the column offset is 1 in order to print the second column.
In the second for
loop, instead of using both row and column offsets the value of rowptr
is incremented to the next row and then a column offset is added to get the second column. Another way of thinking about this is that after incrementing the address in rowptr
, to work with the current row the row offset will be zero.
Therefore, the following statements are equivalent:
*(*rowptr + 1) <==> *(*(rowptr + 0) + 1)
Pointers to Arrays as Function Parameters
When we have an array as a function parameter, we are already passing the address of the array to the function so there is no need to prefix with the address operator. Within the function, we access the array elements just as we did when creating pointers to an array within main()
.
In the following program we create an array of double
s and then use a function to print the array elements.
#include <stdio.h>
void printArray(double*);
int main(void) {
double data[6] = {1.3, 7.9, 2.5, 8.0, 4.1, 9.4};
printArray(data);
}
void printArray(double* ptr) {
double* endptr = ptr + 6;
while(ptr < endptr) {
printf("%p %5.1f\n", ptr, *ptr);
ptr++;
}
}
produces
0xbfbee050 1.3
0xbfbee058 7.9
0xbfbee060 2.5
0xbfbee068 8.0
0xbfbee070 4.1
0xbfbee078 9.4
What is the purpose of the following line?
double* endptr = ptr + 6;
This line creates a pointer to the address past the last array element. After printing the array element pointed to by ptr
, ptr
is incremented (again, using pointer arithmetic) to the next array element until it reaches an address outside the memory allocated for the array. We can see from the output that the memory addresses are eight bytes apart, which is what we would expect if each double
is eight bytes wide.
As in some earlier examples, instead of creating a pointer to navigate through the array I could have used pointer notation to reference each array element. The function declaration and definition remain the same, but inside the function I use offsets to access the specific array elements.
#include <stdio.h>
void printArray(double*);
int main(void) {
double data[6] = {1.3, 7.9, 2.5, 8.0, 4.1, 9.4};
printArray(data);
}
void printArray(double* ptr) {
int i;
for(i = 0; i < 6; i++)
printf("%p %5.1f\n", ptr + i, *(ptr + i) );
}
produces
0xbf84f480 1.3
0xbf84f488 7.9
0xbf84f490 2.5
0xbf84f498 8.0
0xbf84f4a0 4.1
0xbf84f4a8 9.4
Why didn’t I use the array name as I would have had the for
loop been in main
? Remember that because of variable scope, the array name is unknown inside the function and therefore I needed to create some way to reference it.
To work with 2D arrays is similar to what we described in the section Pointers to Two-Dimensional Arrays.
The following example has two functions, each of which prints the first column of an array. printColumn
increments a pointer to the row of the array.
printColumn2
uses a row offset to get the row to work with.
#include <stdio.h>
void printColumn(int (*rowptr)[3]);
void printColumn2(int (*rowptr)[3]);
int main(void) {
int data[4][3]= { { 1, 2, 3},
{ 4, 5, 6},
{ 7, 8, 9},
{10, 11, 12} };
printColumn(data);
printf("\n");
printColumn2(data);
}
void printColumn(int (*rowptr)[3]) {
/* (*rowptr)[3] is a pointer to the
first row (an array) of data.
*rowptr is the address of one of the rows.
to get the value of a
specific element on the row, we prefix with * again
*/
int i;
for(i = 0; i < 4; i++)
{
printf("%p %2d\n", rowptr, **rowptr);
rowptr++;
}
}
void printColumn2(int (*rowptr)[3]) {
int i;
for(i = 0; i < 4; i++)
printf("%p %2d\n", *(rowptr + i),
**(rowptr + i) );
}
produces
0xbfe30120 1
0xbfe3012c 4
0xbfe30138 7
0xbfe30144 10
0xbfe30120 1
0xbfe3012c 4
0xbfe30138 7
0xbfe30144 10
Notice that in printColumn2
, to get the the address of the current element I use
*(rowptr + i)
while to get its value I use
**(rowptr + i)
These two expressions demonstrate that in order to know how many asterisks to use we must know how many dimensions our array has. Knowing that data
is 2D, it may be easier to understand these expressions if we add the column offset to each:
*(rowptr + i) <==> (*(rowptr + i) + 0)
and
**(rowptr + i) <==> *(*(rowptr + i) + 0)
Summary
It’s normal when first learning to use pointers to be confused by when to use an asterisk and how many to use. The answer depends on what the pointer points to and how you are using it. In particular, when working with array names, we need to keep in mind whether or not we have enough information to determine which array element to get the value of.
When we create a pointer to an int
, the concept of row and column offsets doesn’t make sense; the address of the element gives us everything we need to know. Therefore, if we have the following code segment
int x = 7;
int* ptr;
ptr = &x;
to change or use the value stored in x
we can simply prefix the variable ptr
with the indirection operator, *
. Using *ptr
is like using x
.
When we have a one-dimensional array, the name of the array is a reference to the beginning of the array. That alone is not enough to get the value of a specific array element other than the first element of the array. We must add an offset of how far the specific array element is from the beginning of the array. Once we have added the offset to the address of the beginning of the array, we can then prefix this address with the indirection operator to use the value. If we have
int data[] = {1, 2, 3, 4};
Then data + 0
, or just data
, is the address of the first array element, data + 1
is the address of the second element, and so forth. To get the value of the second array element, we use *(data + 1)
.
When we wish to get the value of an element of a two-dimensional array, we have to not only consider how many columns the array element is from the beginning of the array, but also how many rows. Therefore we use two offsets.
Let’s look at an example to see how this works.
#include <stdio.h>
int main(void) {
int num[2][3] = { {25, 26, 27},
{28, 29, 30} };
int* numptr = &num[0][0]; /* get address of first
element of the array */
int i, j;
for(i = 0; i < 2; i++) {
for(j = 0; j < 3; j++)
printf("%d %p", num[i][j], &num[i][j]);
printf("\n");
}
printf("\n");
/* address of num array */
printf(" num is %p\n", num );
/* address of num array */
printf(" *num is %p\n", *num );
/* address of second row */
printf(" num + 1 is %p\n", (num + 1) );
/* address of second row */
printf(" *(num + 1) is %p\n", *(num + 1) );
/* address of second row, third column */
printf(" *(num + 1) + 2 is %p\n", *(num + 1) + 2 );
/* value of second row, third column */
printf("*(*(num + 1) + 2) is %d\n", *(*(num + 1) + 2) );
printf("\n");
/* why only one star here? because numptr
points to an int, not an array of ints */
for(i = 0; i < 6; i++)
printf("%d ", *numptr++); /* this uses *numptr,
then increments */
}
produces
25 0012FF68 26 0012FF6C 27 0012FF70
28 0012FF74 29 0012FF78 30 0012FF7C
num is 0012FF68
*num is 0012FF68
num + 1 is 0012FF74
*(num + 1) is 0012FF74
*(num + 1) + 2 is 0012FF7C
*(*(num + 1) + 2) is 30
25 26 27 28 29 30
Let’s look at the individual parts of the expression for accessing the value of the last element of the array.
num <== address of beginning of array
*(num + 1) <== address of beginning of row 1 (since counting starts at 0)
*(num + 1) + 2 <== address of element at row 1, column 2
*(*(num + 1) + 2) <== value at row 1, column 2
By the time we get *(num + 1) + 2
, we have added the necessary offsets from the base address to get to the last array element. At this point prefixing it with an indirection operator gives us the value of the last array element.
In this same example, we also created a variable, numptr
. When we use it, we only use a single indirection operator to get the value of an array element. Why? The reason is because numptr
is a pointer to an int and we initialized it to the first value of the array with
int* numptr = &num[0][0];
Since num[0][0]
is the first array element, prefixing it with the address operator gives us its address. This is really no different than in earlier examples when we created variables of type int
and then created pointers to them.
Prefixing numptr
with a single asterisk gives us the value of the array element whose address is currently stored in numptr
. In the for
loop of the example above, the value that numptr
points to is printed and then numptr
is incremented so that it points to the next array element.
The overall thing to keep in mind is that in order to determine what you will get from a certain pointer expression requires that you know the type of pointer and what it points to. If num
is a pointer to an int
, then *num
gives you the value of the int that num
points to. If num
is a two-dimensional array, then *num
will give you the address of the beginning of the array.