In the C programming language, pointers are used to refer to the memory locations in which variables are stored. Some code snippets are shown below which demonstrate why they are useful and how to properly use them; the full pointers.c
program and its output are included at the end.
In C, the &
operator is used for accessing the memory address of a variable; for example:
int x = 10;
printf("The value stored in x is %i\n", x);
printf("The address in which x is stored is %p\n", &x);
// The value stored in x is 10
// The address in which x is stored is 000000000023FE44
NB The use of the %p
specifier within the printf
function is explained here.
The *
operator is used for creating a pointer variable which can store a memory address:
int* x_address = &x; // &x is the memory address of x
printf("The value stored in x_address is %p\n", x_address);
// The value stored in x_address is 000000000023FE44
The *
operator is also used for returning the value which is stored in a memory location given by a pointer (this is sometimes called 'dereferencing', and can be thought of as the inverse of the &
operator for returning a pointer to the address of a variable):
int x_value = *x_address; // *x_address is the value stored at x_address
printf("The value stored in x_value is %i\n", x_value);
// The value stored in x_value is 10
Note that only pointer variables can be dereferenced; the statements int y = &x; int z = *y;
will cause a compiler error because y
is not a pointer variable, yet it is trying to be dereferenced by writing int z = *y
(a compiler warning will also occur from trying to store the pointer &x
in the int y
). This line of code can be fixed by making sure that y
is a pointer variable by writing int* y = &x; int z = *y;
.
In summary:
Syntax | → | Description |
---|---|---|
&variable_name |
→ | Address of variable_name |
variable_type* pointer_name |
→ | Pointer called pointer_name to a variable of type variable_type |
*pointer_name |
→ | Value of variable stored at pointer_name (this is a dereferenced pointer) |
If the value at memory location x_address
is changed, the change will be reflected in x
and *x_address
, but not in x_value
. This is because the line int x_value = *x_address;
above created a new variable x_value
at a new location in memory, and assigned to it the value which was then stored in x_address
; but changing the value stored in x_address
later on does not change the value stored in &x_value
.
*x_address = 11;
printf("The new values are %i %i %i\n", x, *x_address, x_value);
// The new values are 11 11 10
This is how not to change a variable within a function, since a copy of the variable's value is passed to the function, and changes to that copy are not reflected in the original variable:
void inc(int x) {
x++;
}
int main() {
int x = 10;
printf("The value of x is %i\n", x);
// The value of x is 10
inc(x);
printf("The value of x is %i\n", x);
// The value of x is 10
}
In order to change a variable within a function, the function must accept as arguments pointers to the variable, which are then dereferenced inside the function:
void inc_pointer(int *x) { // Argument is a pointer variable
(*x)++; // The pointer variable is dereferenced and its value is modified
}
int main() {
int x = 10;
printf("The value of x is %i\n", x);
// The value of x is 10
inc_pointer(&x); // Argument is the memory address of x
printf("The value of x is %i\n", x);
// The value of x is 11
}
Note that C does not support C++-style pass-by-reference.
Array variables store the memory location of the first element in the array; array variables can be dereferenced, and the value given will be the first element in the array. Integers can be added to array variables, and dereferencing those expressions will give successive elements in the array (until the end of the array is reached, at which point behaviour is undefined).
int array[] = {13, 29, 51};
printf("Array contains %i, %i, %i\n", array[0], array[1], array[2]);
printf("Array contains %i, %i, %i\n", *array, *(array + 1), *(array + 2));
// Array contains 13, 29, 51
// Array contains 13, 29, 51
Note in the snippet below, pointer arithmetic is performed automatically: array + 1
goes to the next memory location after array
, but because each integer requires 4 bytes, array + 1
will skip forwards 4 bytes in memory:
printf("Memory locations %i, %i, %i\n", array, (array + 1), (array + 2));
// Memory locations 2358816, 2358820, 2358824
Pointers can be assigned to array variables, and the pointer arithmetic works the same way:
int* p = array;
printf("Array contains %i, %i, %i\n", p[0], p[1], p[2]);
printf("Array contains %i, %i, %i\n", *p, *(p + 1), *(p + 2));
// Array contains 13, 29, 51
// Array contains 13, 29, 51
There are differences between pointers and array variables, however. Using the sizeof
operator will return different results for the two variables, because the array variable stores information about the full size of the array (bytes per element * number of elements = 3 * 4 = 12), whereas the pointer variable only stores the address of the array (which requires 8 bytes on a 64-bit OS), and no information about its length:
printf("Sizes are %i and %i", sizeof(array), sizeof(p));
// Sizes are 12 and 8
The loss of information about the size of the array when a pointer is assigned to that array is known as pointer decay. NB Every time an array is passed to a function, it decays to a pointer.
Another difference between pointers and array variables is that pointers can be reassigned to different locations in memory, whereas array variables cannot (although the values stored in an array can be changed): the statement int array[] = {14, 30, 52};
after array
was already defined would give a compiler error.
Pointers can be used to point to struct
variables, for example:
typedef struct {
int waist_inches;
int leg_inches;
float price;
int quantity;
char* name;
} trousers;
int main() {
trousers jeans = {34, 32, 15.00, 12, "Jeans"};
trousers* pjeans = &jeans;
return 0;
}
pjeans
is now a pointer to the jeans
variable. One way to access the fields of the variable that pjeans
points to would be to use brackets and dereferencing, as follows:
printf("Waist size = %i inches\n", (*pjeans).waist_inches);
// Waist size = 34 inches
A shortcut notation (saving a whopping 2 characters) is to use the ->
operator:
printf("Waist size = %i inches\n", pjeans->waist_inches);
// Waist size = 34 inches
This is often done when passing struct
variables into functions; it is generally preferable to pass a pointer to a struct
variable into a function rather than the struct
variable itself, because otherwise a copy of the struct
variable must be made each time the function is called, which may be inefficient. As before, the ->
operator is used to access the fields of the struct
which is being pointed to:
void print_total_value(trousers* pt) {
float tvalue = pt->price * pt->quantity;
printf("Total value of %s is $%.2f\n", pt->name, tvalue);
}
int main() {
trousers jeans = {34, 32, 15.00, 12, "Jeans"};
trousers* pjeans = &jeans;
print_total_value(pjeans);
// Total value of Jeans is $180.00
return 0;
}
There is a special syntax for defining pointers to functions. Take, for example, the following function:
int add(int x, int y) {
return x + y;
}
The syntax for defining a pointer variable called padd
for this type of function is as follows:
int (*padd) (int, int);
This pointer can be assigned to the function and used as follows:
padd = add;
printf("Result of using function pointer is %i", padd(3, 4));
// Result of using function pointer is 7
In general, the syntax for a function pointer is:
return type (*
pointer name) (
list of parameter types)
One example of when function pointers are useful is for functions which take another function as a parameter, for example qsort
in stdlib.h
, which has the following function declaration (taken from the online reference), and takes a function pointer as its final parameter:
void qsort (void* base, size_t num, size_t size,
int (*compar)(const void*, const void*));
The parameter int (*compar)(const void*, const void*)
refers to a pointer called compar
to a function which takes two parameters which are both pointers to const void
variables, and returns an int
. Using further information from the online reference, the following snippet demonstrates how to define a function compare
which can be passed as an argument to qsort
, which can then be used to sort arrays of int
s (note that the pointers px
and py
must be cast to int
pointers before they can be dereferenced, otherwise the compiler would have no way of knowing how to dereference them):
int compare(const void* px, const void* py) {
int x = *(int*) px;
int y = *(int*) py;
return x - y;
}
The following snippet inserted in the main
function demonstrates this function being tested:
int list[] = {5, 1, 4, 8, 2};
printf("\nThe original list is:\n");
for (int i = 0; i < 5; i++) printf("%i ", list[i]);
qsort(list, 5, sizeof(int), compare);
printf("\nThe sorted list is:\n");
for (int i = 0; i < 5; i++) printf("%i ", list[i]);
// The original list is:
// 5 1 4 8 2
// The sorted list is:
// 1 2 4 5 8
A similar notation can be used to define an array of function pointers; say for example the following functions had been defined:
int subtract(int x, int y) {return x - y;}
int multiply(int x, int y) {return x * y;}
int divide(int x, int y) {return x / y;}
An array of pointers to these functions can be defined as follows:
int (*func_list[]) (int, int) = {add, subtract, divide, multiply};
Arrays of function pointers can be especially useful when each function corresponds to a different possible state of a struct
type variable, each function takes a struct
type variable as an argument, and the struct
contains an enum
type variable which describes each possible state of the struct
type variable, and in the same order as the corresponding function array. Then, when iterating through an array of the struct
type variables, the appropraite function for each struct
type variable can be called depending on its state in a single line as follows (without having to write switch
/case
logic for each possible state of the struct
type variable):
for (i = 0; i < NUM_S; i++) {
func_list[s[i].state](s[i]);
}
For a more concrete example, see Head First C; Dawn Griffiths, David Griffiths; O'Reilly Media, Inc.; April 2012, chapter 7.
/* Compile and run by entering in terminal:
gcc pointers.c -o pointers && pointers
*/
#include <stdio.h>
#include <stdlib.h>
void inc(int x) {
x++;
}
void inc_pointer(int *x) { // Argument is a pointer variable
(*x)++; // The pointer variable is dereferenced and its value is modified
}
typedef struct {
int waist_inches;
int leg_inches;
float price;
int quantity;
char* name;
} trousers;
void print_total_value(trousers* pt) {
float tvalue = pt->price * pt->quantity;
printf("Total value of %s is $%.2f\n", pt->name, tvalue);
}
int add(int x, int y) {
return x + y;
}
int compare(const void* px, const void* py) {
int x = *(int*) px;
int y = *(int*) py;
return x - y;
}
int subtract(int x, int y) {return x - y;}
int multiply(int x, int y) {return x * y;}
int divide(int x, int y) {return x / y;}
int main()
{
// Create a variable, print its address
int x = 10;
printf("The value stored in x is %i\n", x);
printf("The address in which x is stored is %p\n", &x);
// The value stored in x is 10
// The address in which x is stored is 000000000023FE44
// Store the address in a pointer variable
int *x_address = &x; // &x is the memory address of x
printf("The value stored in x_address is %p\n", x_address);
// The value stored in x_address is 000000000023FE44
// Store the value in a new variable
int x_value = *x_address; // *x_address is the value stored at x_address
printf("The value stored in x_value is %i\n", x_value);
// The value stored in x_value is 10
int *y = &x; int z = *y; printf("z = %i\n", z);
// Change the value stored in x_address
*x_address = 11;
printf("The new values are %i %i %i\n", x, *x_address, x_value);
// The new values are 11 11 10
// How not to change x within a function
x = 10;
printf("The value of x is %i\n", x);
// The value of x is 10
inc(x);
printf("The value of x is %i\n", x);
// The value of x is 10
// How to do it properly
inc_pointer(&x); // Argument is the memory address of x
printf("The value of x is %i\n", x);
// The value of x is 11
// Arrays act similar to pointers
int array[] = {13, 29, 51};
printf("Array contains %i, %i, %i\n", array[0], array[1], array[2]);
printf("Array contains %i, %i, %i\n", *array, *(array + 1), *(array + 2));
// Array contains 13, 29, 51
// Array contains 13, 29, 51
printf("Memory locations %i, %i, %i\n", array, (array + 1), (array + 2));
// Memory locations 2358816, 2358820, 2358824
int* p = array;
printf("Array contains %i, %i, %i\n", p[0], p[1], p[2]);
printf("Array contains %i, %i, %i\n", *p, *(p + 1), *(p + 2));
// Array contains 13, 29, 51
// Array contains 13, 29, 51
printf("Sizes are %i and %i\n", sizeof(array), sizeof(p));
// Sizes are 12 and 8
trousers jeans = {34, 32, 15.00, 12, "Jeans"};
trousers* pjeans = &jeans;
printf("Waist size = %i inches\n", (*pjeans).waist_inches);
// Waist size = 34 inches
printf("Waist size = %i inches\n", pjeans->waist_inches);
// Waist size = 34 inches
print_total_value(pjeans);
// Total value of Jeans is $180.00
// Function pointers
int (*padd) (int, int);
padd = add;
printf("Result of using function pointer is %i", padd(3, 4));
// Result of using function pointer is 7
int list[] = {5, 1, 4, 8, 2};
printf("\nThe original list is:\n");
for (int i = 0; i < 5; i++) printf("%i ", list[i]);
qsort(list, 5, sizeof(int), compare);
printf("\nThe sorted list is:\n");
for (int i = 0; i < 5; i++) printf("%i ", list[i]);
// The original list is:
// 5 1 4 8 2
// The sorted list is:
// 1 2 4 5 8
// Array of function pointers
int (*func_list[]) (int, int) = {add, subtract, divide, multiply};
return 0;
}
>gcc pointers.c -o pointers && pointers
The value stored in x is 10
The address in which x is stored is 000000000023FE0C
The value stored in x_address is 000000000023FE0C
The value stored in x_value is 10
z = 10
The new values are 11 11 10
The value of x is 10
The value of x is 10
The value of x is 11
Array contains 13, 29, 51
Array contains 13, 29, 51
Memory locations 2358784, 2358788, 2358792
Array contains 13, 29, 51
Array contains 13, 29, 51
Sizes are 12 and 8
Waist size = 34 inches
Waist size = 34 inches
Total value of Jeans is $180.00
Result of using function pointer is 7
The original list is:
5 1 4 8 2
The sorted list is:
1 2 4 5 8