Pointers

Learn about the purpose of pointers in C, about declaring and dereferencing them, and about the implications of the size of a pointer.

Pointers represent one of the more powerful features of the C language, but also one of the most feared. Some of this fear and apprehension stems from the ridiculously obtuse ways that pointers may be used in C. Often tutorials and courses on C approach teaching pointers by asking the student to decipher bizarre looking pointer syntax combinations that truthfully are rarely used in practice. You may have seen bizarre looking pointer syntax, (and you may see it again), but typically, pointers do not have to be used in a horribly complicated way. Typically pointers are pretty straightforward.

The basic idea is that a pointer is a special data type in C, that contains an address to a location in memory. Think of a pointer as an arrow that points to the location of a block of memory that in turn contains actual data of interest. It’s not unlike the concept of a phone number, or a house address.

There is a code example below that illustrates this more clearly.

Purpose of pointers

Pointers allow us to work with dynamically allocated memory blocks on the heap. We can also use pointers to deal with stack variables, but in most cases this just isn’t necessary.

As pointers can be used to manually, directly access a block of memory, they are used a lot for strings and structs. It’s not difficult to imagine that passing a large block of memory (such as a struct that contains many things) to a function would require copying all that data on the stack. Passing the address of such a block of memory is more efficient than making a copy of it and passing in that copy, only to delete that copy when your function is done with it. Passing the address of a variable to make it accessible in a function is called passing by reference. Passing the value of a variable by allocating a new local variable and copying its value into it is called passing by value.

A working example

To use a pointer, we need to know how to declare a pointer, how to store an address of a memory location in it, and how to use it to access that memory location.

Declaring a pointer

Here is how to declare a pointer and store an address in it:

Press + to interact
#include <stdio.h>
int main(void)
{
int age = 30;
int *p;
p = &age;
printf("age = %d\n", age);
printf("p = %p\n", p);
printf("*p = %d\n", *p);
printf("sizeof(p) = %ld\n", sizeof(p));
*p = 40;
printf("*p = %d\n", *p);
printf("age = %d\n", age);
return 0;
}

Let’s first run through lines 5–7 visually before seeing an explanation of how the pointers are declared and used in the code.

Press + to interact
Variables declared in main are allocated on the stack before it is invoked
Variables declared in main are allocated on the stack before it is invoked
1 of 3

  • On line 5, we declare an int variable age and initialize it with 30.

  • On line 6, we declare a variable p whose type is a pointer to an int.

    The star * syntax is how we declare the type of a variable as a pointer to a particular other type. Usually we need to declare pointers as pointing to a particular type, but the only exception is a so-called void pointer, which is a pointer to an unknown type. Don’t worry about void pointers for now.

    At this point, we have a variable p that is a pointer to an int, but it doesn’t point anywhere. We have only declared that we want a space in memory for the pointer.

  • On line 7 is where we assign a value to our pointer p. We assign to p the address (the location) of the other variable age.

    When the & operator is used in C before a variable name, the memory address of that variable is retrieved.

    So now at this point in the program, we have the following values stored in the variables (which we can see in the output):

    • The value in the variable age is the integer 30.
    • The value in the variable p is the address of the age variable.
    • The variable to which p points has a value of 30.
  • On line 9, the value of the p pointer is printed out. When the program is run, the output can be seen as a long strange looking string of letters and numbers. This is in fact a hexadecimal memory address.

Dereferencing a pointer

Accessing the value that a pointer points to is called dereferencing a pointer. This can be done by using a star * followed by the pointer name—outside of a declaration. For example, in the code shown above,

  • On line 10, we see how to access the value that the pointer p points to, by using the *p syntax. So p is the address of an int variable, whereas *p is the value of the int variable that p points to.

  • On line 12, we use pointer dereferencing again, but this time to set the value of the variable pointed to by p to 40. Since p points to the age variable, we are setting the value of the address in memory corresponding to the age variable to 40.

Size of a pointer

From line 11 in the above code and the corresponding output, we can see that our pointer p is 8 bytes.

Remember, there are 8 bits in a byte, so a 4-byte pointer can hold up to 32 bits, or 232 distinct values, i.e. 232 distinct addresses in memory. So 32-bit pointers allow us to access up to 4 gigabytes (4 GB) of memory. If we want to access more than 4 GB of memory we’ll need bigger pointers! On 64-bit systems, pointers are 8 bytes, which allows for 264 distinct addresses in memory. It will be a long time before our computer has that much RAM (16 exabytes in principle, which is 1 billion gigabytes or 1 million terabytes).

Another powerful use of pointers is that they allow us to communicate with arrays.