Pointers in C are one of the most difficult things for many programmers to wrap their minds around and understand. I’m no exception, so this C tutorial on pointers was a re-learning experience for me.
Pointers are a powerful mechanism to work on any array data type. They are also one of the most dangerous tools a programmer can use and a source of a large share of frustrating programming bugs.
In fact, some languages, like Java, have banned their use altogether.
Also, arrays and pointers in C share many similarities, and can be used interchangeably in a lot of instances, as we’ll soon see.
In spite of all this, once you learn how to use pointers you’ll be glad you took the time to do so.
Pointers in C
What are Pointers?
A pointer in C is a variable whose value is a memory address. Pointers indirectly refer to (or point to) other variables or part of their contents. While a variable normally contains a specific value, a pointer contains the address of a variable that contains a specific value. So, a variable name directly references a value, and a pointer in C indirectly references a value. Figure 1 sheds some light on this concept.
Figure 1: how pointers in C work. While variables directly reference a value (like “count” directly references 7), pointers indirectly reference a variable by ‘pointing to’ the address of a variable (like “countPtr” points to the variable “count” which contains the value 7).
Pointers enable programs to create and manipulate dynamic data structures (data structures that grow and shrink) like queues, stacks, linked lists and more.
Because the hardware instructions of computers and microcontrollers rely heavily on addresses, pointers enable you to express yourself in a way that is close to how the machine expresses itself. This makes programs with pointers efficient.
Pointers also offer an efficient way to deal with arrays. In fact, arrays are simply pointers in disguise. More on this shortly.
Declaring Pointers in C
Like other variables, you must declare a pointer before using it. For example, the declaration
int *pointing, number;
declares the variable pointing to be of type int. You can read the declaration to say “pointing is a pointer to an integer.” The variable number is just a standard integer, not a pointer to an integer. The asterisk (*) in the declaration applies only to pointing. Each variable you want to declare as a pointer needs to be preceded by an asterisk. So, if I want to declare two pointers, I need to do something like the example below.
int *ptr1, *ptr2;
The * is known as the indirection operator or the dereferencing operator. The type specification identifies the type of variable the pointer points to and the * identifies the variable itself as a pointer. It is not enough to say that a variable is a pointer; you need to tell the compiler what kind of variable the pointer points to. The reason for this is that different variable types take up different amounts of storage and some pointer operations (more on these later) need knowledge of that storage size.
One thing to note is that using a space between the * and the name of the pointer is optional. Some programmers will use the space in a declaration (which I did not do) and omit it when dereferencing a pointer. If you’re not sure what dereferencing means, we’ll talk about it a bit later.
You can use pointers to reference (or point to) floats, longs, chars, and a host of other data types, not just ints.
Consider the declaration below.
char *broil; // broil is a pointer to a character variable
The value of what broil points to is of type char but what about broil itself? The value of broil is an address which many systems represent as an unsigned integer. However, don’t think of a pointer as an integer type. You can do things with pointers you can’t do with integers and vice versa. For example, you can divide one integer by another, but you can’t divide one pointer by another. We’ll talk about what kind of arithmetic you can do with pointers a bit later.
The Address Operator
Before we venture deeper into the thickets of pointer forest, a few words on the address operator are in order.
In many languages, the address of any given variable is the computer’s business and the programmer does not get that information. However, in C you can access the address of a variable using the address operator which is an ampersand (&).
Though you can print the address of a variable if you’re really that curious about it that’s not the main use of the address operator. Using the address operator with pointers in C gives you the ability to manipulate addresses and their contents symbolically.
Become the Maker you were born to be. Try Arduino Academy for FREE!
When followed by a variable name, the address operator gives the address of that variable. For example, &myHouse gives the memory address of the variable myHouse.
Suppose we declare a pointer with the name ptr. We can write a statement like the one below:
ptr = &myHouse; // this assigns myHouse’s address to ptr
We can say that ptr points to myHouse. However, the actual value of ptr is the memory address, in hexadecimal, of the variable myHouse. If you’re unfamiliar with hexadecimal numbers I suggest you read Breaking the Hex: Understanding Hexadecimal Numbers.
Let’s say myHouse is an integer whose value is 1234. If we use the indirection operator, we can dereference pointer ptr this way:
num = *ptr // assigns value at location ptr to num
by doing this, we assign the value 1234 to num.
Let’s look at a simple program that swaps 2 numbers to demonstrate pointers and the address operator.
#include <stdio.h> void swapper (int *a, int *b); /* function prototype. For a review on functions see C Programming Tutorial 5 */ int main (void) { int y = 10, z = 20; printf(“First y = %d and z = %d. \n”, y, z); swapper (&y, &z); /* call function and send it addresses of y and z */ printf(“Now y = %d and z = %d. \n”, y, z); return 0; } void swapper (int *a, int *b) /* start the function definition */ { int temp; // temporary storage spot temp = *a; /* dereferencing a so temp gets value a points to */ *a = *b; *b = temp; }
Notice that instead of passing the explicit values of y and z we pass the addresses of y and z to swapper() which indirectly references the variables. Though C does not explicitly support pass by reference, this is a way to closely emulate it. However, C++ does support pass by reference, but this is a C tutorial so we shall say no more about that for now.
Also, we need a third variable in the function definition to temporarily hold the value of integer a.
Finally, it may be tempting to write something like
temp = a;
instead of
temp = *a;
But a has the value of &y, so it points to y which means *a gives the value of y (in other words, we’re dereferencing a). If we don’t use the indirection operator, we’ll likely get a memory address rather than the value we want.
Arrays and Pointers in C
Earlier we made the claim that arrays and pointers we basically the same thing. Let’s talk a bit more about that.
First, if you need a review on arrays in C, check out C Programming Tutorials 6 and 7 which discuss arrays.
Believe it or not, an array name is also the address of the first element of the array. So, if swagger is an array, then the following is true:
swagger == &swagger[0]; // name of array is the address of the first element
Both swagger and &swagger[0] represent the memory address of the first element.
Various systems and platforms use different amounts of bytes to represent different data types. For example, in the Arduino IDE an integer takes up 2 bytes but on some other system it may take up 4 bytes.
This is one reason why you have to declare the sort of object to which a pointer points. The address is not enough because the computer needs to know how many bytes it needs to store the data type. Figure 2 gives a simplified graphical depiction of this concept for clarification.
Figure 2: pointer ptr pointing to an array of integers. Notice it contains the address of the first element, not the value of the first element. Here, each integer takes up 4 bytes, so the address of the first integer is 3000, the second is 3004 and so on. Other systems may use 2 byte integers in which case the second element of the array would have address 3002, the third 3004 and so on.
The take-away is that the value of a pointer is the address of the object to which it points. How the address is represented internally is hardware dependent. That’s why we need to use data types when declaring pointers.
The relationship between arrays and pointers means that you can often use either one when writing a program. They’re usually interchangeable. However, there may be instances where using one over the other can yield benefits such as execution speed.
To show this is the case, consider the following program that prints the number of days in each month.
#include <stdio.h> #define MONTHS 12 int main(void) { int days[MONTHS] = {31,28,31,30,31,30,31,31,30,31,30,31}; for (int i = 0; i < MONTHS; i++) printf("Month %d has %2d days.\n", i+1, days[i]); return 0; }
We can change the code within the main() function and accomplish the same result, as we can see below.
int main(void) { int days[MONTHS] = {31,28,31,30,31,30,31,31,30,31,30,31}; for (int i = 0; i < MONTHS; i++) printf("Month %d has %2d days.\n", i+1, *(days + i)); // *(days + i) is same as days[i] return 0; }
We still use an array to hold the number of days in each month, but when we print the days, we use a pointer instead. The addition in the pointer may be confusing to some. Let’s talk about that.
Pointer Operations and Arithmetic
Pointers in C are valid operands in arithmetic expressions, assignment expressions and comparison expressions. But not all operators that normally work with other data types work with pointers. For example, you can’t multiply one pointer by another.
Let’s talk about what you can do with pointers in C.
Dereferencing Pointers
As we already know, a pointer can be dereferenced which gives the value in the address it points to. To dereference a pointer, we use the indirection operator (a.k.a. dereferencing operator) which is an asterisk (*).
There is one caveat to watch for when dereferencing pointers. Be careful not to dereference an uninitialized pointer. As an example, consider the code snippet below.
int *ptr; // uninitialized pointer *ptr = 100; // error
This is a big no-no because the second line of code is telling the compiler to store the value 100 in the location to which ptr points. But ptr is uninitialized and has a random value, so there is no knowing where the 100 will go. It might end up somewhere harmless, it may overwrite data or code, or it might cause the program to completely crash.
Getting an Address
We already know that we can use the address operator to get the memory address of a variable. Like all variables, pointers also have an address. The & operator can tell you where the pointer itself is stored.
Assignment
You can assign an address to a pointer. Usually, you do this by using an array name or by using the address operator (&). The code fragment below illustrates this.
int array1[5] = {1,2,3,4,5}; int *ptr1; ptr1 = array1; // assigns an address to pointer ptr
When doing this the address should be compatible with the pointer type. In other words, don’t try to assign the address of a float to a pointer to an integer.
Adding an Integer to a Pointer
As our example program that prints the number of days in each month shows, you can add an integer to a pointer, and this can be quite useful. You can also add a pointer to an integer. In either case, the integer is multiplied by the number of bytes in the data type the pointer points to (refer to figure 2), and the result is added to the original address. The code fragment below shows this.
int array1[5] = {1,2,3,4,5}; int *ptr1, *ptr2; ptr1 = array1; // assigns an address to pointer ptr ptr2 = ptr1 + 4; //same as &array1[4] *ptr2 = ?????
In the fragment above we dereference ptr2 which gives us the fourth element of array1 giving us a value of 4.
There is one caveat to be aware of when adding integers to pointers. The result is undefined if it lies outside of the array into which the original pointer points, except that the address one past the end element of the array is guaranteed to be valid. So, using the code fragment above, if I write
ptr2 = ptr1 + 17;
the result is meaningless since the array only has five elements.
Just as we can add an integer to pointer, we can also subtract integers from pointers. As is similar to the addition case, the integer is multiplied by the number of bytes in the data type the pointer points to and the result is subtracted from the original address. And, as in addition, the result is undefined if it lies outside of the array into which the original pointer points, except that the address one past the end element of the array is guaranteed to be valid.
Incrementing and Decrementing Pointers in C
Programmers can use the ++ or – – operators to increment or decrement pointers just like other variables. This is useful when using pointers to step through loops. Incrementing a pointer to an array element makes it move to the next element of the array. Similarly, decrementing a pointer to an array element makes it move to the previous element of the array. Keep in mind that when doing either/or the actual address of the pointer itself remains the same.
Finally, you can use both the prefix and postfix forms of the increment and decrement operators.
Consider the code fragment below.
int array1[5] = {5,4,3,2,1}; int *ptr1; ptr1 = array1; // assigns an address to pointer ptr ptr1++;
In the example above ptr1 was originally equal to the first element of the array (5) but at the end of the code it increments by one and is now equal to the second element of the array (4).
One last thing to note is that pointer arithmetic is meaningless unless you use a pointer that points to an array. Do not assume that two variables are contiguous (next/near to each other) in memory unless they are adjacent elements of an array.
Differencing Pointers in C
You can find the difference between two pointers. Normally, you do this for two pointers to elements that are in the same array to find out how far apart the elements are. The result is in the same units as the type size. Note that if you subtract one pointer from another and they point to integers, and, say the result is 2, this indicates they are separated by 2 integers not necessarily by 2 bytes.
Applying this operation to pointers to two different arrays might produce a value or could lead to a runtime error.
Comparisons
You can use the relational operators to compare the values of two pointers if the pointers are of the same type and in the same array. Pointer comparisons compare the addresses in the pointers. Such a comparison may show, for example, that one pointer points to a higher numbered element of the array then the other. One common use is determining if a pointer points to nothing (i.e. it is 0).
Pointing Isn’t so Rude After All
So now we some basics about pointers in C and how to use them. We’ve seen that they’re not so hard to understand after all and can be very useful in your programs.
As usual, this is a somewhat deep subject which is capable of filling several posts. Because of this, I’m sure pointers will come up again in future C tutorials and other articles.
Until then, drop a comment and tell me about your thoughts on pointers. Do you use them? Do you prefer arrays, pointers, or use both equally? Is there any important information concerning pointers I left out? I’d love to hear from you!
Become the Maker you were born to be. Try Arduino Academy for FREE!
Electronics Tips & Tutorials Sent Directly to Your Inbox
Submit your email & you'll get:
- Exclusive content that I don't put on the blog
- The checklist 10 mistakes all electronics enthusiasts make (& how to avoid them)
- And more!
jimmy says
I’ve always found pointers difficult to understand but this really helped clear some of the confusion. Thanks!
Brian says
You’re very welcome Jimmy! Glad I could help clear the confusion.
d4 says
USeful, thanks.
common mistake of array numbering done here:
—
In the fragment above we dereference ptr2 which gives us the fourth element of array1 giving us a value of 4.
—
doesn’t ptr2 + 4 == &array1[4] give us the *fifth* element of the array? indexing starts at [0].