≡ Menu

Pointers in C

This is a tutorial – sort of – on pointers in C, written around 1995 or so. Reading it now is a little painful for me.

What are pointers?

Pointers are, basically, references to a spot in memory. There are two operators associated with pointers; the * and & operators. (This isn’t quite true; see Pointers and Structs and Arrows, Oh My for more on this.)

The & operator returns an address of the variable immediately following. For example, if a variable x is at location 0xff00 (for example), &x will return 0xff00.

The * operator dereferences an address. It can be “read” in this way: *pX is the value at address contained in the variable pX.

Note that using the expression *(&x) is the same as just plain x.

How do you use pointers?

Let’s play out a very simple scenario. Let’s assume that we have a requirement for a function that returns an incremented integer. A function prototype would thus be: int iAddOne(int iValue).

This function is remarkably simple… and because I’m a nice guy, I’ll write it for you:

int iAddOne(int iValue)
    {
    return iValue+1;
    }

Note that this leaves iValue alone; a program that called iValue would have to assign the return value of iAddOne() to iValue explicitly to change iValue. This is because on a call to iAddOne(), the compiler generates a copy of iValue, puts it on the stack, then calls iAddOne(), which pulls the copy of iValue off the stack, adds one to it, and puts the new value on the stack… and then returns. The calling program then pulls the new value off the stack and does whatever it wants to it.

Now let’s look at a different example, one using pointers. First, our function requirements: Write a function that modifies the passed parameter and returns nothing. (Note: this means our function is a good one; it only does one thing and that’s it.)

void AddOne(int* piValue)
    {
    *piValue=*piValue+1;
    }

Now let’s look at a call to our two functions, to see how they work.

   ...
    int iCounterFirst=0;
    int iCounterNext;
    ...
    /* let's use the function that leaves iCounterFirst alone */
    iCounterNext=iAddOne(iCounterFirst);

    ...

    /* Now let's do it again, except now we change iCounterFirst */
    AddOne(&iCounterFirst);
    ...

In the first section, assuming the value of iCounterFirst is 0, after the function call to iAddOne(), iCounterNext will be 1, and iCounterFirst will remain at 0. After the call to AddOne(), we’re passing the address of iCounterFirst (the expression &iCounterFirst) and AddOne() modifies the int at the address it’s passed (the value at &iCounterFirst). Therefore, iCounterFirst will be 1 after the call to AddOne().

This is a good tip to help this become even clearer: get a debugger. Learn how to use it. Then use it on a code snippet like this, and create a watch expression on every variable expression you come across. In the example given above, for instance, you would create a watch expression on iCounterFirst, iCounterNext, and &iCounterFirst. If you used the same variable names that I did for the iAddOne() and AddOne() functions, you’d also create local watches for iValue, piValue, and *piValue as well. If you can, examine the stack, too, and watch the variables being pushed on and popped off the stack if you’re willing to watch assembly language execute. When you step over the pre for the call to AddOne(), note that piValue will be the same as &iCounterFirst, and *piValue will be the same value as iCounterFirst.

Pointers and Structs and Arrows, Oh My

You can create a pointer to a struct, just like you can create a pointer to an int (as shown above). However, a structure pointer has a special operator associated with it, the -> operator. The -> operator allows you to refer to a member of a structure pointer. Here’s an example.

struct data_t
    {
    int iFirstItem;
    double dSecondItem;
    };

struct data_t Data;
struct data_t* pData; /* note that pData is a pointer to struct data_t */

/* here's the standard array reference... */
Data.iFirstItem=1;
Data.dSecondItem=2.0;

/* now let's make *pData and Data the same */
pData=&Data;

/* Here's our structure pointer notation */
pData->iFirstItem=pData->iFirstItem+4;

Note that pData->iFirstItem is the same as Data.iFirstItem, and it’s also identical to (*pData).iFirstItem — note that the parentheses are required in this expression because of operator precedence.

Conclusion

That’s really all there is to it. Play with pointers; they’re immensely useful (and critical if you want to do acceptable and manageable programming in C). If you don’t understand them, write short snippets of code like the ones above and watch them closely. Try to figure out exactly what’s going on inside.