Pointers in C with examples

Contents


What is a pointer?

A variable X,

int X;
occupies some memory. X_ptr
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”.

&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;
}

(download)

prints out something like
x = 42
x_ptr = 0xbfbfd92c

The value of x is always the same, but x_ptr differs 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;
}

(download)

prints out something like

Get the existing values of foo, bar, foo_ptr, and * foo_ptr:
foo = 42
bar = -1
foo_ptr = 0xbfbfd91c
* foo_ptr = 42
Change the value of * foo_ptr:
foo = 99
bar = -1
foo_ptr = 0xbfbfd91c
* foo_ptr = 99
Change the value of foo_ptr to & bar:
foo = 99
bar = -1
foo_ptr = 0xbfbfd918
* 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;
}

(download)

prints out

0xbfbfd924
0xbfbfd924
0xbfbfd924

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;
}

(download)

prints out

 first element: 45 (0xbfbfd880)
second element: 67 (0xbfbfd884)
 third element: 89 (0xbfbfd888)

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;
}

(download)

prints out

89

Here's a diagram:

The second element of array_ptr is the third element of array.

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;
}

(download)

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);
}

(download)

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);
}

(download)

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;
}

(download)

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;
}

(download)

This one uses indexing, which uses a pointer.


Copyright 2005–2010 Peter Hosey. Copyright 2011-2014 Ben Bullock. This work is licensed under a Creative Commons Attribution 2.5 License. This version is a modification of the original text from boredzo.org/pointers by Ben Bullock. For comments, questions, and corrections, please email Ben Bullock (benkasminbullock@gmail.com). / Privacy / Disclaimer