Pointers in C with examples
Contents
What is a pointer?
A variable X
,
int X;
occupies a space in the computer's memory. This space is called its
address. A pointer like X_ptr
here:
int *X_ptr = &X;
contains the location of X
in memory. X_ptr
is called “a pointer to X”.
X
's location in memory is called its “address”.
The ampersand symbol in front of X, &X
, gives the
address of X
. The ampersand symbol &
is
called the “address-of operator”.
The following program,
#include <stdio.h> int main () { /* x is an integer variable. */ int x = 42; /* x_ptr is a pointer to an integer variable. */ int * x_ptr = & x; printf ("x = %d\n", x); printf ("x_ptr = %p\n", x_ptr); return 0; }
prints out something like
x = 42 x_ptr = 0xbfbfe8ec
The value of x
will always be the same, but the value
of x_ptr
will differ from computer to computer.
Assignment and pointers
Given an integer variable foo
and a pointer to
it, foo_ptr
, a value can be assigned to foo
using foo_ptr
. To access the value at a pointer,
*foo_ptr = 42;
To read the value,
int bar = *foo_ptr;
The “dereference operator” *
accesses the value at an address.
#include <stdio.h> int main () { int foo = 42; int bar = -1; int * foo_ptr; foo_ptr = & foo; printf ("Get the existing values of foo, bar, foo_ptr, and * foo_ptr:\n"); printf ("foo = %d\n", foo); printf ("bar = %d\n", bar); printf ("foo_ptr = %p\n", foo_ptr); printf ("* foo_ptr = %d\n", * foo_ptr); printf ("Change the value of * foo_ptr:\n"); * foo_ptr = 99; printf ("foo = %d\n", foo); printf ("bar = %d\n", bar); printf ("foo_ptr = %p\n", foo_ptr); printf ("* foo_ptr = %d\n", * foo_ptr); printf ("Change the value of foo_ptr to & bar:\n"); foo_ptr = & bar; printf ("foo = %d\n", foo); printf ("bar = %d\n", bar); printf ("foo_ptr = %p\n", foo_ptr); printf ("* foo_ptr = %d\n", * foo_ptr); return 0; }
prints out something like
Get the existing values of foo, bar, foo_ptr, and * foo_ptr: foo = 42 bar = -1 foo_ptr = 0xbfbfe8dc * foo_ptr = 42 Change the value of * foo_ptr: foo = 99 bar = -1 foo_ptr = 0xbfbfe8dc * foo_ptr = 99 Change the value of foo_ptr to & bar: foo = 99 bar = -1 foo_ptr = 0xbfbfe8d8 * foo_ptr = -1
Arrays
Here array
is declared as an array containing three
integers:
int array[3] = { 45, 67, 89 };
In C, in most places, the name array
becomes a pointer
to its first element. Most usages of array
are equivalent
to if array
had been declared as a pointer.
For example, if an array is passed to printf
, the
array name becomes a pointer:
printf ("%p\n", array);
prints the address of array[0]
.
#include <stdio.h> int main() { int array[3] = {45, 67, 89}; printf ("%p\n", array); printf ("%p\n", & array); printf ("%p\n", & array[0]); return 0; }
prints out
0xbfbfe8c4 0xbfbfe8c4 0xbfbfe8c4
This is called “decaying”. Decaying is an
implicit &. array ==
&array == &array[0]
. These expressions are read
“array
”, “pointer
to array
”, and “pointer to the first element
of array
”.
Pointer arithmetic
#include <stdio.h> int main () { int array[] = { 45, 67, 89 }; int * array_ptr; array_ptr = array; printf(" first element: %i (%p)\n", *array_ptr, array_ptr); array_ptr++; printf("second element: %i (%p)\n", *array_ptr, array_ptr); array_ptr = array_ptr + 1; printf(" third element: %i (%p)\n", *array_ptr, array_ptr); return 0; }
prints out
first element: 45 (0xbfbfe8b0) second element: 67 (0xbfbfe8b4) third element: 89 (0xbfbfe8b8)
The ++
operator adds 1 to a variable. When adding to a
pointer, the amount is multiplied by the size of the type of the
pointer. In the case of our two increments, each 1 was multiplied
by sizeof(int)
.
If int is four bytes, adding 1 to an int pointer changes it by four bytes.
Indexing
The subscript operator []
in array[0]
actually works on pointers.
#include <stdio.h> int main () { int array[] = { 45, 67, 89 }; int *array_ptr = & array[1]; printf("%i\n", array_ptr[1]); return 0; }
prints out
89
Here's a diagram:
Array
points to the first element of the
array; array_ptr
is set to &array[1]
, so
it points to the second element of the
array. So array_ptr[1]
is equivalent
to array[2]
. Array_ptr
starts at the second
element of the array, so the second element of array_ptr
is the third element of array
.
Because the first element is sizeof(int)
bytes wide,
the second element is sizeof(int)
bytes ahead of the
start of the array. Array[1]
is equivalent
to *(array + 1)
. The number added to a pointer is
multiplied by the size, so 1
adds sizeof(int)
bytes to the pointer value.
Structures
A structure is created with the struct keyword.
A struct looks like this:
struct foo { size_t size; char name[64]; };
Each declaration in the block is called a member. A member is accessed like this:
struct foo my_foo; my_foo.size = sizeof(struct foo);
The expression my_foo.size
accesses the
member size of my_foo
.
For a pointer to a structure, it is possible to use
(*foo_ptr).size = new_size;
or the pointer-to-member
operator, ->
.
foo_ptr->size = new_size;
With multiple indirection:
(*foo_ptr_ptr)->size = new_size; /* One way */ (**foo_ptr_ptr).size = new_size; /* or another */
Multiple indirection
Consider
int a = 3; int *b = &a; int **c = &b; int ***d = &c;
Here are how the values of these pointers equate to each other:
*d == c;
**d == *c == b
***d == **c == *b == a == 3;
The & operator can be thought of as adding asterisks, and the *, ->, and [] operators as removing asterisks.
Pointers and const
The const keyword is confusing when pointers are involved. These two declarations are equivalent:
const int *ptr_a; int const *ptr_a;
These two, however, are not equivalent:
int const *ptr_a; int *const ptr_b;
*ptr_a
is constant; you cannot do *ptr_a =
42
, but ptr_b
is constant; you can
assign *ptr_b
, but not the pointer itself. Attempting to compile
int main () { int a; int b; int const * ptr_a; int * const ptr_b; ptr_a = & a; * ptr_a = 42; ptr_b = & b; * ptr_b = 42; return 0; }
produces errors of the form
const.c:8:13: error: read-only variable is not assignable * ptr_a = 42; ~~~~~~~ ^ const.c:9:11: error: read-only variable is not assignable ptr_b = & b; ~~~~~ ^ 2 errors generated.
Function pointers
It's possible to take the address of a function. Function names
decay to pointers. So for the address of strcpy
, use
either strcpy
or &strcpy
.
When you call a function, you use an operator called the function call operator. The function call operator takes a function pointer on its left side.
In this example, strcpy_ptr
is a pointer to a
function strcpy_like
.
#include <stdio.h> #include <string.h> /* An ordinary function declaration, for reference */ char *strcpy_like(char *dst, const char *src); /* Pointer to strcpy-like function */ char *(*strcpy_ptr)(char *dst, const char *src); #define str_length 18 int main () { char src[str_length] = "This is a string."; char dst[str_length]; /* Set the value of "strcpy_ptr" to be "strcpy". */ strcpy_ptr = strcpy_like; /* This works too */ strcpy_ptr = & strcpy_like; (*strcpy_ptr) (dst, src); printf ("dst = %s\n", dst); } char * strcpy_like(char * dst, const char * src) { return strcpy (dst, src); }
This prints out
dst = This is a string.
The parentheses around *strcpy_ptr
separate the
asterisk indicating return type (char *)
from the asterisk indicating the pointer level of the variable
(*strcpy_ptr
— one level, pointer to function).
Parameter names are optional:
/* Parameter names removed — still the same type */ char *(*strcpy_ptr_noparams)(char *, const char *) = strcpy_ptr;
The type of the pointer to strcpy is char *(*)(char *, const char *). This is the declaration above without the variable name. In a cast,
strcpy_ptr = (char *(*)(char *dst, const char *src))my_strcpy;
A pointer to a pointer to a function has two asterisks inside the parentheses:
char *(**strcpy_ptr_ptr)(char *, const char *) = &strcpy_ptr;
An array of function-pointers:
char *(*strcpies[3])(char *, const char *) = { strcpy, strcpy, strcpy }; strcpies[0](dst, src);
A function f with no parameters returning an int, a function fip with no parameter specification returning a pointer to an int, and a pointer pfi to a function with no parameter specification returning an int.
int f(void); int *fip(); /* Function returning int pointer */ int (*pfi)(); /* Pointer to function returning int */
A function pointer can be a return value:
char *(*get_strcpy_ptr(void))(char *dst, const char *src);
returns a pointer to a function of the form
char *strcpy (char *dst, const char *src);
Typedefs simplify:
#include <stdio.h> #include <string.h> /* An ordinary function declaration, for reference */ char *strcpy_like(char *dst, const char *src); /* The following declares "strcpy_funcptr". */ typedef char *(*strcpy_funcptr)(char *, const char *); /* Declare a function which returns a pointer to a function. */ strcpy_funcptr get_strcpy_ptr (void); /* This function takes a pointer to a function as an argument. */ void do_strcpy (strcpy_funcptr some_strcpy, char * dst, const char * src) { /* Here is where we finally call the function. */ (*some_strcpy) (dst, src); } #define str_length 18 int main () { char src[str_length] = "This is a string."; char dst[str_length]; /* This declares "strcpy_ptr" using the above typedef. */ strcpy_funcptr strcpy_ptr; /* Set the value of "strcpy_ptr" using "get_strcpy_ptr". */ strcpy_ptr = get_strcpy_ptr (); /* Pass the pointer to "do_strcpy". */ do_strcpy (strcpy_ptr, dst, src); printf ("dst = %s\n", dst); } strcpy_funcptr get_strcpy_ptr (void) { return & strcpy_like; } char * strcpy_like(char * dst, const char * src) { return strcpy (dst, src); }
This prints out
dst = This is a string.
Strings
C strings are arrays of char
:
char str[] = "I am the Walrus";
This array is 16 bytes in length: 15 characters for "I am the Walrus",
plus a NUL (byte value 0) terminator. In other
words, str[15]
(the last element) is 0. This signals the
end of the string.
The functions in string.h
work on char *
pointers.
Here's an implementation of strlen
which returns the
length of a string, not including the NUL terminator:
size_t strlen (const char *str) { size_t len = 0U; while (*(str++)) ++len; return len; }
Here's another possible implementation:
size_t strlen(const char *str) { size_t i; for (i = 0U; str[i]; ++i) ; /* When the loop exits, i is the length of the string */ return i; }
This one uses indexing, which uses a pointer.