Programming in C: Pointers
This article is part of a series – How to Program Anything: C Programming
So far we’ve covered basic variable types, and their extension the array in the previous Programming in C posts (here, and here). This post focuses on the third major component when it comes to dealing with variables, values and memory: the pointer. A pointer is basically a variable that holds the memory address of a piece of data. Since C is a middle-level language it still has remnants of assembly language in its operation. What I mean by that is that in higher level languages such as PHP or Python, you will rarely if ever have to deal with the specific memory addresses of any variables, but in C it is common place. Pointers are necessary in C in order to support dynamic memory allocation, to link together disparate pieces of data like in a linked list, to pass original data structures to functions that then can modify them, and such.
Unfortunately, pointers can be somewhat confusing and even more unfortunate, lead to bizarre errors. This is because they are extremely powerful in their ability to point to any address in memory of the whole computer. Use an pointer that hasn’t been initialized to something within your own program and you may be overwriting your own code, the stack, even possibly the memory space of your operating system.
Used properly pointers are very convenient ways to address memory and deal with contiguous blocks of space, like arrays. Without pointers you would not be able to allocate dynamic blocks of memory in your heap. Pointers can even point to functions, and allow you to pass functions to other functions, in effect switching out and plugging in different functionality. In this respect C does have limited functional programming properties.
What Is A Pointer
A pointer is a variable that, rather than storing a direct value from the programmer, stores a memory address. This address is the location of any number of things, including memory blocks, functions, and other variables. When one variable contains the address of another variable, it is said that the first variable points to the second variable, or function, or what have you.
The above diagram illustrates what I’m talking about. The variable located at memory address 0x005 contains the value 11. However, the variable located at memory address 0x008 contains the memory address of the first variable. The arrow shows how that memory address, when resolved, points to the value stored at 0x005. In this respect the variable stored at 0x008 is a pointer, and points to the value locate at 0x005.
How To Define A Pointer
As in all the rest of C, in order to have a variable act as a pointer, it must be defined as a pointer up front. To do this requires a small extension to the basic variable declaration we covered in Basic Data Types. Whenever there is a type declaration that is intended to be a pointer you put a asterisk after the type specification. So if below for instance, I define a variable of type char, and then proceed to define a pointer variable of type char *:
NOTE: From here on out when I write comments in code snippets I will sometimes use the C89 comments (starting with /* and ending with */) but in general for smaller comments I will use the C99 single line commenting system (anything starting with a //)
// Declare a normal variable: char myNormalVariable; // Declare a pointer variable: char *myPointerVariable;
It is important to note that a pointer variable, though technically able to point to any address in memory, has a base type defined in its declaration. You don’t just define a pointer, you define an int pointer or a char pointer. This is very important when it comes to the pointer’s behavior later on in your programming. This is because all pointer operations, as we’ll cover soon, are relative to the type of the pointer.
There is such a thing as a void pointer. This is otherwise known as a generic pointer. This pointer is a general purpose pointer and though powerful, is somewhat limited in pointer operations. It is meant to point to anything, many times temporarily holding an address in memory until it can be cast to a pointer of a more specific type.
Yes, one type of pointer can be converted into another type of pointer. This is known as pointer conversion. This is first and foremost where the types of pointers become evident. If you cast a double pointer to an int pointer, and then use that int pionter to assign a value, you’re not going to get a double, you’re going to get only the first four bytes: an int. We’ll examine this closer in pointer assignment.
The Pointer Operators
We have not discussed operators up until now, but put simply operators are basically statements/symbols that perform actions on any given variable/piece of data. For example in the statement 2 + 5, the + is the addition operator and takes two operands, it resolves/returns the value of 7. For pointers there are two unary (meaning it only takes one operand) operators that enable us to work with memory addresses. These are * and &.
The & Operator
The & operator is fairly easy to understand. It is a unary operator that takes whatever expression/variable that comes after it and returns its memory address. I personally read the & operator as being “the address of” whatever variable I’m addressing at the moment. It is important to be able to resolve a variable to its memory address so that we can assign that address to a pointer. Examine the code below:
// Define a integer variable int myIntegerVariable = 7; // Define an integer pointer int *myIntegerPointer; // Cause the pointer to point to the variable myIntegerPointer = &myIntegerVariable;
If we had not used the & operator in the last statement, we’d be assigning the value 7 to the pointer. This would cause the pointer to point to memory address 7, and if we were to use it later we’d get a major error. As it is, with the & operator, myIntegerPionter now contains the memory address of myIntegerVariable.
The * Operator
The * operator is a little harder to understand, in my opinion. One reason is because it shares the same symbol with pointer type declarations, and when you mix the * operator with pointer type declarations (type *) it can get a little weird. The other reason is because you can use the * operator on a variable inside of an assignment statement, which reads odd to me. I shall explain.
The * operator is the conceptual opposite of the & operator in that instead of returning a memory address, it instead returns the value that is stored at the memory address held by the pointer. It is a unary operator just like the & operator. To further our example we might write something like this after the above example:
// Another integer variable int anotherIntegerVariable; // Assign the value of the pointer to another anotherIntegerVariable = *myIntegerPointer;
In this example, anotherIntegerVariable now contains the value originally stored in myIntegerVariable above. I like to read the * operator as saying “at the address of.” Although, to be honest that’s a little clunky to me, and instead I really imagine it as simply a resolution operator. It resolves whatever memory address is being stored and accesses that memory address. The reason I say this is because this kind of code is possible:
// integer variable int myVar; // Integer pointer int *myPtr; // Assign pointer myPtr = &myVar; // Assign 7 to myVar through the pointer *myPtr = 7;
Did you see that last line? In that line I assigned the number 7 to the variable stored at the memory address of myPtr, which in this case is myVar; At the end of execution of the above block myVar is equal to 7. That’s why I imagine something like *myPtr to indicate that the compiler is resolving that expression to the memory address stored, in this case, it is equivalent to myVar.
Arrays of Pointers
Pointers can also be defined in arrays. You can index them just as you would normal values. For example you can create a ten element array with this easy declaration. I also show examples of assigning to a particular element in the array as well as resolving from an element in the array.
char *myPointers; // assign a memory address myPointers = &someVariable; // resolve a memory address anotherVariable = *myPointers;
Note that this is different than what are known as dynamically allocated arrays, which are to be covered in another article.
Pointers to Pointers
It is actually possible to have a pointer point to another pointer. This is known as multiple indirection. It is most useful when dealing with various memory systems and their corresponding issues. A pointer to a pointer is no different than a pointer to a normal object in terms of storing and resolving memory addresses. However, their declaration is different from the standpoint of the compiler:
// Define a pointer to a pointer char **myMultipleIndirection;
Note the TWO asterisks. These indicate that this is a pointer to… another pointer. This bit of code may illustrate the idea here better than I can in a paragraph:
char myValue = "a'; char anotherValue; char *myPointer; myPointer = &myValue; myMultipleIndirection = &myPointer; anotherValue = **myMultipleIndirection; // this sets anotherValue to 'a' **myMutipleIndirection = 'd'; // this sets myValue to 'd'
As you can see, we must assign myMultipleIndirection, define as (char **), to the address of a pointer. This is because it is a pointer that points to a pointer. This type of indirection is a bit esoteric, but is used often. Just remember that pointers are variables themselves, and can have their own memory addresses too!
Pointer Assignment (Conversion)
You’ve seen basic pointer assignment in the examples given for the pointer operators above. If you want to assign a pointer to the memory address of something, you use the & operator. If you want a pointer to contain the same memory address as another pointer (in effect point to the same thing) you simply assign one pointer to another without any additional operators:
// My Pointers int *p1; int *p2; p1 = p2;
What if you have different types of pointers but you want them to point at the same thing? This is where pointer conversion comes in. C has a method called “explicit casting”, where you can force a variable/pointer to pretend like it’s another type so that the compiler doesn’t complain that types don’t match. To do casting, you place the type in parentheses before the expression. So let’s assign char pointer to an int value:
char *myPtr long int myVariable; myPtr = (char *) &myVariable;
This is valid code. However, you have to remember, as noted above, that a char pointer will always act like a char. It will only reference one byte. If we were to assign another variable by resolving myPtr, you’ll only get the first byte of the integer myVariable, since chars only store one byte, while a long int stores four.
Ah yes, the dreaded pointer arithmetic. It’s not really as complicated as word on the street has made it sound. You just have to remember that all operations are done relative to the base type of the given pointer. First, let’s define the constraints. You can only perform two operations on pointers, and those are addition and subtraction. In fact, you can only add integers to pointers, not float or double values. You cannot add two pointers together, though you can subtract them (we’ll get to that).
What happens when you increment or decrement a pointer? It depends entirely on the base type of the pointer. The rule though is that the pointer will point to the next memory address that could contain the base type. So for example, if we increment a char pointer, we’ll get the next byte relative to its contained memory address. This is because a char takes up one byte. However, if we increment a long int pointer, we’ll jump four bytes relative to its contained memory address. And this is because a long int takes up four bytes. The diagram below shows this graphically:
Every time we add 1 to the (long int *) pointer we advance by four bytes, whereas every time we add 1 to the (char *) pointer we advance by one byte. This is because the pointer advances according to its base type size. If the base type of a pointer took up 23 bytes, then every time we advanced the pointer by 1 it would point 23 bytes ahead in memory. Pointer arithmetic occurs relative to the size of the base type of the pointer.
You can subtract one pointer from another pointer of the same base type. This will give you the number of base type object between the two. That is, if you subtract one (long int *) from another (long int *) pointer, you’ll get the number of long ints that exist between the two memory addresses.
It is possible to compare one pointer to another pointer. The pointer that points to the lower memory address will come out as being smaller. This is useful when we are dealing with pointers and arrays, or pointers that are pointing to the same object or variable. Any of the various comparison operators (>, <, >=, <=) can be used in regards to the pointers.
Pointers at this point in the article series may seem like more of a pain that a boon, but they are very powerful when it comes to constructing articulate programs. Pointers allow us to pass the address of objects in memory around to functions, and allow us to point to dynamically allocated memory. Pointers and their relationships to arrays, as well as their relationship to dynamic memory allocation is the subject of articles to come in the series. For now, pointers for us simply point to a position in memory. As a round up, remember that the & operator gives us the memory address of a variable, and the * operator resolves a stored memory address. With those two elements in mind pointers don’t have to be intimidating, and in fact can be fun. I hope I’ve helped you learn something from this tutorial, thanks for reading!
This article is part of a series – How to Program Anything: C Programming
If you appreciate this article you might consider supporting my Patreon.
But if a monthly commitment is a bit much, I get it, you might consider buying me a coffee.