Pointers are easy

If you are thinking about learning C or C++, but have been “warned” by some that it’s really difficult, then chances are that conversation was about pointers. When I first started learning some more low-level languages, the reactions I got were either something like: “Ouch, I never really got the hang of that pointer business, you know…” on the one hand. The other reaction I got was: “Have fun, get it out of your system. Once you realize what that entails, you’ll be begging to get back to a high-level language”.

Who was right? I honestly have to say that neither of these two groups got it quite right. Sure pointers were confusing at first, and sure, writing a program that reads files, and works some magic with strings is a lot easier in python than it is in C, but I’ve really grown to like C. As for pointers: As far as I’m concerned, they really, really, really aren’t that hard. All you have to do is stick with it, until the penny drops.

What follows are a couple of the common pitfalls people encounter when first trying to use pointers in C and, perhaps more importantly, how to avoid them.

Pointers can point to immutable data

One of the more common problems encountered is when people are trying to work with strings in C. Unlike more high-level languages, there is no string type. There are char‘s (single characters), and a string is but an array of characters, topped-off with a nul-character (0, or '').
Arrays and pointers are two very, very different things, but this difference isn’t always obvious at first (more on the matter later). For now, let’s look at the 2 different ways for declaring strings, and what the main gotcha is here:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main (int argc, char **argv)
{
    char *string1 = "foobar",
         string2[] = "foobar";
    size_t str_len;
    int i;
    str_len = strlen(string1);
    for (i=0;i<str_len;++i)
    {
        if (*(string1+i) == string2[i+1])
            string2[i] = '@';
    }
    printf("%s => %s\n", string1, string2);
    return EXIT_SUCCESS;
}

For those of you who can’t work out what the output will be: This code is absolutely safe to compile and run. The output you should get is this:
foobar =&gt; f@obar

So? You can clearly see that there are 2 strings being declared: a char *string1, and a char string2[]. What’s the big deal? For that, you’d have to try running a slightly altered version of the same code. In fact, changing the string declaration will break the program already:

    char *string1 = "foobar",
         *string2 = "foobar";//both of type char *
    //...code here...
        string2[i] = '@'; //<--- FAILS!

But why does this fail? For that, you need to understand what a pointer really is. A pointer, as I’m sure any book will tell you, is a variable that holds the memory address of a value. Through this address, you can get at the actual data, and use it in computation. So in this code char *string2 = "foobar"; is declaring a pointer to a char, and this pointer is being initialized using a string. What, then, is being assigned to this pointer. It sure can’t be the value of this string, because that wouldn’t be a valid pointer.

What actually happens is that the string that is being assigned ("foobar") is set aside in memory. The pointer points to a constant, hard-coded string, and therefore its value is stored in read-only memory. A more complete, clear-cut and accurate declaration, then, would be one that specifies the storage class that is being used:

    const char *string1 = "foobar";
    auto char string2[] = "foobar";

Read this as “A pointer to a constant char called string1”. The second declaration reads as “An array of the type char, long enough to accommodate “foobar” (7 chars: foobar + ”)”. This array will be created and the constant string “foobar” will be copied into it. Read this again, thing about it, let it sink in if you must, but things should be making sense now. If you grasp this, you should realize that, depending on where the pointer is pointing to, you can or cannot edit the value itself.

Arrays can be used as pointers, and pointers can be used as arrays

Using the same code example, I’m going to attempt to demonstrate one of the more common misconceptions: Pointers and arrays both can be used in the same way 80-90% of the time. When you start to learn C/C++, the code you write is of course rather basic, too, so the array-pointer interchangeability rate is probably closer to 95%.
This probably is one of the major reasons why a lot of people get confused when their array or pointer is behaving differently to what they were expecting. In the code snippet I gave earlier, I’ve used a const char *string1, and when dereferencing it (using a pointer to actually get at the data it contains), I’ve used pointer arithmetic. When I used the array, I stuck to the trusted, and well known array notation (string2[i]).

Now I hope you’re sitting down, because I’m going to re-write that for loop a couple of times. All of the snippets are equally valid, and do the exact same thing, yet they look rather outlandish from time to time. All of the code remains the same, except for the bit inside of the loop:

    for (i=0;i<str_len;++i)
    {//using pointers as if they were arrays
        if (string1[i] == string2[i+1])
            string2[i] = '@';
     //using arrays as pointers
        if (*(string1 + i) == *(string2 + i + 1))
            *(string2 + i) = '@';
     //using arrays as offsets on arrays or pointers
        if (i[string1] == (i+1)[string2])
            i[string2] = '@';
     //mixing all of the above
        if (string1[i] == *(string2 + i + 1))
            i[string2] = '@';
    }
}

Now, as incredible as it may seem, all of the above versions are perfectly valid C. They all work, and all produce the exact same result. As you can see, you can use a pointer as if it were an array, and an array can be used as a pointer (in most cases), which goes a long way in explaining why people seem to confuse the two a lot.
What might be news to your, or just look like an insane (and erroneous) construction is the snippet where it would appear that I’m using an int (i) as an array, and a pointer, or array as an offset (i[string2] is just all shades of wrong, right?). Well, you’d be surprized. Just try this out:

int array[] = {123, 32, 12, 0};
printf("array[%d] = %d\n", 2, 2[array]);

The output will be array[2] = 12. So that’s got the code looking confusing, don’t you think? Well, let’s see if we can’t make it any more confusing by using the last element in our array (0) to get the code above to put out array[0] = 123. A fun exercise would be to write the code that does this in such a way so that it can do this no matter how big the array is (the snippet to that will be added to the bottom of this post).  Basically, the 3 in the code below should become a variable:

int array[] = {123, 32, 12, 0};
printf("array[%d] = %d\n", 3[array], (3[array])[array]);

I’m going to end the array fun here, and rather spend some time actually explaining why all of these notations work. And the explanation is really rather simple, dull and obvious. The C standard defines the behavior of our beloved language. It also defines how compilers must (or must not) implement various operators, including the [] operator.
Its behavior is defined as this:

a[b] = *(a  + b)

In the standard case, this means that a  is an array name (or pointer), and b is an index or offset. This is possible due to the fact that an array name resolves to the address of the first element in the array (ie, the name array is a pointer to array[0]).
Also note that the dereferencing * operator dereferences the result of a+b, which explains why a[b] == b[a] == *(a + b) == *(b + a).
In short the behavior can be explained if you understand these three basic rules:

  • An array name resolves to a pointer to the first element of that array (ie decays into a pointer)
  • using the `[]` operator is the same as writing `*(mem_adderss + offset)` where mem_address can be either a pointer or an array name
  • Because the addition of the address and the offset occurs prior to dereferencing, the offset value and memory address can be swapped safely.

Arrays are NOT pointers

After all this, you’d be tempted to say that arrays are, actually pointers. That’d be a dangerous mistake, lie and the source of a huge amount of problems for the time to come. Though you could argue that pointers are sort-of like arrays, there’s absolutely no guarantee of that. If you have an array of characters, you have a block of memory that can store a certain amount of characters in a row. Because if this, an array is, in a way, aware of its own size. Pointer are not. They know how big they are, which means: they know how big a memory address is (typically 4  bytes on a 32bit machine, 8 on a 64bit platform), but they do not know how big the block of memory is they’re pointing to. They don’t even know if they’re pointing to a valid block or not. Let’s look at an example:

char a_string[] = "A string";
const char *a_string_ptr = "A string";
int array[] = {123, 32, 12, 0};//an array
int *p;//a pointer
printf("Size of a_string: %zu\n", sizeof a_string);
printf("size of a_string_ptr: %zu\n", sizeof a_string_ptr);
printf("size of p: %zu\n", sizeof p);
printf("size of array: %zu\n", sizeof array);

this snippet results in the following output:

Size of a_string: 9
Size of a_string_ptr: 8

So far, so good: a_string has to be 9 big, to accommodate the 9 characters that make up the string: 8 chars for “A string” + 1 for the terminating '' character. But then, something rather odd happens. The integer array (array) contains 4 values, but look at the output:

size of p: 8
size of array: 16

The integer pointer is just as big as a char pointer (which makes sense, considering both variables have to contain a memory address), but the array containing just 4 integers is a whopping 16 bytes big. How come? To understand this, add the following to the code:

printf("Size of type int: %zu\n", sizeof(int));
printf(
    "size %zu divided by %zu => %zu elements\n",
    sizeof array,
    sizeof array[0],//array[0] == *array
    sizeof array/sizeof *array
);

Now things should start falling together: the array is indeed 16 bytes big, but each int takes up 4 bytes, so to determine the actual size of the array, we have to divide its total size by the size of each element, hence sizeof array/sizeof *array. Why didn’t we have to do this with the character array? That’s simply because, as defined by the C standard, the type char is guaranteed to be 1. An int has to be at least twice the size of a char, but is most commonly implemented as 4 bytes big.

Ok, I know what you’re thinking. Something along the lines of “And I should care beccause….? Why?”. To which the answer is: because C has such a thing as functions. If you pass an array to a function, you do so by passing the variable name as an argument. Remember that an array name evaluates to the address of the first element of that array? Well, to put it in other words: passing an array to a function will see that array decay into a pointer. Time for another example:

void initialize_array(char *array, size_t length)
{
    int i;
    //check for null pointers, because dereferencing
    //null pointers will give you grief
    if (array == NULL)
        return;//no array to work with
    for (i=0;i<length;++i)
        array[i] = '.';
    array[length -1] = '\0';//add terminating char
}

int main ( void )
{
    char buffer[100];
    initialize_array(buffer, sizeof buffer);
    printf("%s\n", buffer);//99 dots
    return 0;
}

As you can see, the buffer array becomes a pointer once it’s been passed to the initialize_array function. Inside that function, it has become impossible for us to determine how big the block of memory is we’re trying to initialize. For that, we have to rely on the caller to pass the correct length argument along. If the caller passes 50 instead of 100 in the example above, then no harm done, but if you pass 200, the initialize_array function will just do its job, and write beyond the bounds of the array, resulting in undefined behavior. This is why pointers are said to be dangerous.

So why are pointers easy?

You’d be forgiven for hating me at this point. I started out by stating that pointers are really easy, but all this time, I’ve shown you some really, really basic examples but already there are many situations where pointers can cause undefined behavior, or put you at risk. They require more care, and still I claim they’re easy? Well yes, they are.
What I’ve given you now is a quick introduction to the use of pointers, but what follows here are 2 really simple analogies that’ll help you. They are ways of looking at pointers, thinking about what you’re doing that’ll make things go smoothly:

Pointers are like sliding calendars

If you’re not too young, chances are this looks familiar:

sliding_date_adjuster_25_35100

An old-fashioned sliding calendar, correct. Look at the image, what is that red square doing? It is, in fact, set to point at a particular date. The red box can’t tell you what day it is, what month or if the month in question has 28, 29, 30 or 31 days. All it does is point at a single day. To set it to the next day, you have to add 1 to it. Add 2 for the day after and so on. In C code, using pointer arithmetic, that equates to writing this:

int april[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30};
int *day_indicator = april;//decays into pointer, set to april[0]
//to get to the date in the picture, either write:
day_indicator += 14;//two weeks after 1st of april
//or in a loop:
while (*day_indicator != 15)//while not 15th
    ++day_indicator;//next day

That’ s not hard now, is it? Basically, that’ s simple pointer arithmetic in a nutshell. But look at the picture again, that 15 looks a bit blurry, and the problem with C is, that it’s quite old, and has trouble seeing. If this happens in real life, what you can do is push the red square down a bit. This is what dereferencing a pointer actually does: it’s like telling the machine to look at what is behind the plastic, rather than at the plastic itself. That’s what the * operator does in while (*day_indicator != 15). It says “Push down on the red box, read what is below it, and if it’s  not 15, move it to the next value”. Now how easy is that?

Is this an accurate analogy? Quite. It’s not full-proof, but it’s getting there. Thinking about valid pointers like this will get you there almost 99% of the time. No kidding. It really is that simple.
The thing you have to keep in mind is that, unlike calendars, C has more than one type, and determining what the next memory address will be is done at compile time. If you write a_ptr +=2;, that 2 is applied to the type the pointer is pointing to. If a_ptr points to a char, then the pointer will move 2 bytes in memory. If it’s of the type int, that jump is likely going to be 8 bytes. Whenever you perform pointer arithmetic, think abou the types: you can cast an int pointer to a char *, but realize that pointer arithmetic will be affected by this.

Pointers are direction signs

Because a pointer only tells the user (a function, a block of code) where some piece of data is stored, you could think of it as a direction sign, too. The sign itself can’t tell you anything about the place it’s guiding you towards. That town could be big, small, ugly or beautiful… but that’s irrelevant from the sign’s perspective. All it can, and should, tell you is where to find it.
If you pass a pointer to a function, you’ll often find it useful to pass a size parameter along with it. That’ s like a sign saying “Town X 4 miles”. It’s helpful, and a nice thing to do, but a pointers’ job remains the same regardless:

direction-sign_zps13d35402

So if ever you’re lost in the enigmatic world of pointers, just to your old-fashioned sliding calendar for directions


As promised, a possible solution to the exercise about using pointers and arrays with the unorthodox notations:

#include <stdio.h>
#include <stdlib.h>

void print_values_as_keys(int *arr, size_t elems)
{
    int i;
    if (arr == NULL)
    {
        fprintf(stderr, "Null pointer passed!\n");
        exit( EXIT_FAILURE );
    }
    for (i=0;i<elems;++i)
        if (arr[i] < elems)
            printf(
                "Key %d => arr[%d] = %d\n",
                i, i[arr], *(arr + i[arr])
            );
}

int main ( void )
{
    int array[] = {123, 34, 5, 1, 6, 0};
    print_values_as_keys(array, sizeof array/sizeof *array);
    return EXIT_SUCCESS;
}
Advertisements
This entry was posted in C, howto, pointers, programming, tutorial and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s