What is a Pointer?

If you are reading this, you are probably either:

  1. Just starting to learn about C pointers.
  2. Have a half-formed concept of them and are trying to clear up the confusion.
  3. Or are reviewing this particular guide.

In any case, just keep reading through. All examples are in a tarball. This is an archive made by tar, and compressed by gzip. It should open in File Roller, WinZip, Ark or similar programs. Or you could just use tar xvf and gzip -d like I did. :)

Special thanks go to Arthur J. O'Dwyer, and many others, of comp.lang.c, whose verbose and highly useful criticisms contributed greatly to the development of this guide.


First Some Preliminary Data

RAM, in the form of actual RAM such as SDRAM, as well as swap on disks, is accessed by computers through addressing, that is, associating a numerical address with each unit of memory and using it to obtain the data therein. Different CPU architectures handle this differently, but ultimately, in most cases, it gets abstracted at the software layer. C's dealings with RAM, being a medium-level language, are in the form of variables; there's ints, doubles, chars, etc. We say that a variable has an address and the data stored therein. These are two distinct things. In this text, I'll just pretend that the memory addresses are decimal numbers. In reality, thanks to Konrad Zuse, any architecture's addressing is sure to be in either hexadecimal or octal notation, as it is more natural for computers to use powers of 2. x86 for example has a hexadecimal segment/offset scheme, such as C800:0000. So just remember I am using the decimal notation as a handy abstraction. Also, in the examples below, it will be assumed that each address references a single byte of data. So for example, three pieces of data two bytes in length each, placed in memory consecutively, might be obtained by reading addresses 300 and 301, 302 and 303, 304 and 305.


OK, Then...What is a Pointer?

A pointer is a variable whose content is the address of another variable. It is used as a very flexible referencing scheme to allow very fast modification and use of variables throughout the sections of a program (for example, a pointer can be used to do an emulation of call-by-reference functions). To illustrate their use for a first time, here is an example C source:

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    void foo(int *bar);
    
    int main(void) {
        int frob = 42;
    
        foo(&frob);
    
        fprintf(stdout, "%i\n", frob);
    
        return 0;
    }
    
    void foo(int *bar) {
        fprintf(stdout, "%i\n", *bar);
        *bar = 69;
    }
    
Variable Address Contents
frob 100 42
bar 200 100

This table can be used to imagine the manipulation of the variables throughout the program, starting with the definition of frob, which has an address of 100 (i.e., its address in memory is 100) and the data put in that address 100 is 42, of the type int; remember that the data type (int, pointer to int, char, etc.) means nothing at the level of hardware, in the RAM, but is very important at the abstracted level of the program. At the call to foo, the unary ampersand is used on the variable frob, the operator used to return the address of a variable, and since frob is a int the value returned by this operator is a pointer t int The argument for foo is a pointer t int and thusly the types match; bar's value is now 100, the address of frob. Hence, in effect, frob can be accessed from the function foo!! This is used in the call to fprintf. Note that the format string for fprintf is "%i\n", which means that the argument should be of the type int But bar is a pointer to int The dereference operator, an asterisk in front of a pointer variable, will go to the address stored therein, and return the contents. In this case, the dereference operator will go to address 100 (the value of bar), and return its contents: 42, stored as an int. Therefore, the type yielded by the expression *bar is int, not pointer-t int This next table may help understanding:

Expression Type
frob int
&frob pointer to int
bar pointer to int
*bar int
&bar pointer to pointer to int

The last row indicating type of the expression &bar, is indeed a pointer to pointer. The relevance of them will be revealed yet, though it may not immediately seem obvious.

Note also that derefenced pointers can be used on the left-hand side of a statement, although this is less common. Our example here is: *bar = 69; in foo. What this does is: the program looks at the value stored in bar and instead of yielding the value of the same address, it assigns the value of the right side of the expression to this address. So, returning to main, fprintf will print 69 instead of 42, since frob was indirectly modified by the pointer bar.

Another important thing to remember is what happens when a pointer contains an invalid address, and one attempts a dereference on it. This can result in bad undefined behavior. It may even crash the program, if the value of the pointer is NULL. NULL is defined in stdlib.h as a pointer that does not point to any piece of data. It is 0, optionally cast to void *. (The void pointer is explained below.) When you assign NULL to a pointer, it is a way of saying that the pointer has not come into use yet; I will clarify this use later on. You must remember to include stdlib.h to use NULL; doing otherwise is undefined behavior!

Check out the above example, modified to pass foo's argument a value of NULL:

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    void foo(int *bar);
    
    int main(void) {
        int frob = 42;
    
        foo(NULL);
    
        fprintf(stdout, "%i\n", frob);
    
        return 0;
    }
    
    void foo(int *bar) {
        fprintf(stdout, "%i\n", *bar);
        *bar = 69;
    }
    

Dereferencing a value of NULL will cause what is called a segmentation fault. This is a fatal error and will crash a program. So you must be careful...

One other thing to remember: int's and pointer values are not interchangable. There are two ways to assign to pointers, with the ampersand operator or malloc, to come later.


Pointers and Single-Dimension Arrays

This part can be very confusing, so go over it multiple times if needed and experiment practically with C source.

A thing to keep in mind to start with: Arrays are not pointers. Anyone who says otherwise is wrong. It is true that many of the same operators can be used interchangeably on either of them, but they are not the same.

What is an array? An array is a block of memory allocated for a number of members of a certain data type, and of course this block of memory has consecutive address. so in other words, if the size of int is two bytes, and each address in memory references one particular byte in memory, then the address of each member of an array, int foo[3] might follow something like this:

Expression Value
&foo[0] 400
&foo[1] 402
&foo[2] 404

And since chars are one byte in length , an array of charsuch as char shizzle[3] might have its addresses laid out like so:

Expression Value
&shizzle[0] 500
&shizzle[1] 501
&shizzle[2] 502

And what is a pointer? See above, of course, for a complete rundown, but in short it is a data type that is made to reference other data through its address. A pointer can be changed to 'point at' different locations in memory such as below:

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void) {
        int foo = 42;
        int bar = 69;
        int *baz;
    
        baz = &foo;
        fprintf(stdout, "%i\n", *baz);
    
        baz = &bar;
        fprintf(stdout, "%i\n", *baz);
    
        return 0;
    }
    

The region of data an array accesses cannot be changed. Therefore they are not pointers. But they are similar in some respects.

One of the most important similarities between a pointer and an array is something called 'the equivalence of pointers and arrays'. When an array is used in an expression, the name will change into a pointer to its first element. The type of the pointer will be the type of the array's members, so for an array int foo[5], the use of foo in an expression will turn into a pointer to the first element of foo. Which is the same as &foo[0]. Examine the source below for better understanding, as well as the dissection below and accompanying variable/address table:

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    void baz(char *frob);
    
    int main(void) {
        char *foo;
        char bar[] = "fill in blank\n";
    
        foo = bar;
        fputs(foo, stdout);
    
        baz(bar);
        baz(foo);
    
        return 0;
    }
    
    void baz (char *frob) {
        fputs(frob, stdout);
    }
    
Variable Address
foo 100
bar 130
frob 150

We start out by defining foo and bar. foo is a pointer to char and bar is an array of chars of fifteen elements. In the next line, bar decays into a pointer to its first element, which is of course the address of its first element, address 130. The value of foo is now 130.

Next, a call to fputs. Read documentation for fputs on the web sometime, or a man page. It accepts a pointer to char as its first argument. foo's value, 130, the address of the first byte in the string bar, will be copied into the pointer to char that is fputs' first argument. Since this is a copied pointer, it is wrong to use the ampersand operator on bar, making the call fputs(&foo, stdout). Because if I did, the type would be pointer to pointer to char a type mismatch, and the result would be the address 100, which is not what fputs wants. So just think of the passing of the value of foo to fputs' first argument as copying the pointer. What does fputs do with the pointer to char It reads a piece of data that is the size of a char at the address contained in the first argument, prints it, moves to the next space containing a character (in this case, assuming again that the architecture being used has an address assigned to each byte, and assuming that each char is one byte in size, this is the next address over, 131), and continues this process until it comes to a NUL, terminating the string. If each char was two bytes in length (not likely, but just for the sake of argument), the function might look first at address 130, then 132, then 134, etc. How this is done will be explained later. When passing an array of chars to this function, the array will change to a pointer to its first element, leaving essentially the same result. So if I were to pass bar to fputs, bar would decay into the value of 130, the address of its first element. Note that there is no such thing as 'call-by-value' for arrays in C. You may even see such a function definition as this at times:

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    void bar(int kochba[]);
    
    int main(void) {
        int baz[] = { 1, 2, 3, 4 };
    
        bar(baz);
    
        return 0;
    }
    
    void bar(int kochba[]) {
        fprintf(stdout, "%i\n", *kochba);
    }
    

The int kochba[] seen in the declaration and definition for function bar is misleading. It is really syntactic sugar for int *kochba which is more straightforward considering what's been learned. The form int kochba[] suggests that a whole array is being passed, which is not possible in C. Why does int *kochba make more sense? Note that the call to bar in main passes baz. Being an array of ints, it will obviously change to a pointer to int Hence, int *kochba as the argument for bar makes more sense. It's best not to use the syntactic sugar form seen above and just write it out as it really is:

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    void bar(int *kochba);
    
    int main(void) {
        int baz[] = { 1, 2, 3, 4 };
    
        bar(baz);
    
        return 0;
    }
    
    void bar(int *kochba) {
        fprintf(stdout, "%i\n", *kochba);
    }
    

You may note, in the function bar, that the first member of baz can be accessed by the pointer to the same, kochba. But how about the other three?? This is the subject of the next section.


Basic Pointer Arithmetic

To demonstrate what pointer arithmetic is, I will show an example and then explain it:

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    void bar(int *kochba, int size);
    
    int main(void) {
        int baz[] = { 69, 42, 36, 87 };
    
        bar(baz, 4);
    
        return 0;
    }
    
    void bar(int *kochba, int size) {
        int i;
    
        for (i = 0; i < size; i++) {
            fprintf(stdout, "%i\n", *(kochba + i));
        }
    }
    

Were you to compile this example, its output would be:

    69
    42
    36
    87
    

Note that in the call to fprintf, instead of just *kochba, there is *(kochba + i). This is called pointer arithmetic. When you add a number to a number type, such as an int or double you get regular addition. But when you do it on pointers, the number is automatically multiplied by the size (in bytes) of the type to which the pointer points. Confused? Ok, let me elaborate. Pointers, as explained, contain a numerical value, which is a particular address in memory. In our examples, and in most settings, in real life as well, each address is assigned to a byte of data. So let's put the array baz in the example above in another table:

Expression Result
&baz[0] (or just baz) 700
&baz[1] 702
&baz[2] 704
&baz[3] 706

The value of kochba in function bar will be 700. In the first iteration of the for loop, zero (the initial value of i) is added to kochba, and then dereferenced, so the integer at address 700 is accessed. Next iteration, since the size of an int is 2 bytes in our examples, adding i (whose value is now 1) to kochba will result in 702, not 701. This is correct because the location of the next integer in the array baz is two bytes, and therefore two addresses away. The data in address 702 is retrieved with the dereference operator and returned. In the next iteration, since i is 2, the result is 704. And in the last iteration of the for loop, of course, since i is 3, the data in address 706 is accessed with the dereference operator. Now why is kochba + i in parentheses? This is because dereference takes precedence over the arithmetic operators; in this case the arithmetic needs be done first. First, pointer arithmetic must be done on kochba, and then the result is dereferenced. If the expression was *kochba + i, kochba would be dereferenced, and then the result would be added to i in regular arithmetic. If I did that as seen below:

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    void bar(int *kochba, int size);
    
    int main(void) {
        int baz[] = { 69, 42, 36, 87 };
    
        bar(baz, 4);
    
        return 0;
    }
    
    void bar(int *kochba, int size) {
        int i;
    
        for (i = 0; i < size; i++) {
            fprintf(stdout, "%i\n", *kochba + i);
        }
    }
    

...this is what would would print out:

    69
    70
    71
    72
    

Try to ponder the difference carefully to see if it helps understanding. What happens in the last example is that, due to the fact that pointer arithmetic is not applied to kochba, the address 700 is dereferenced every time, and then i is added in regular arithmetic.

The array member access operator can also be used on pointers, which is a source of confusion. In fact, the [] brackets from which we are used to getting members of an array really only operate on pointers when you think about it. Remember that an array will 'decay' into a pointer to its first element; the array member access brackets are really made to operate on pointers, and do essentially the same thing as pointer arithmetic. So if there is an array of ints called foo, foo[2] is nearly identical to *(foo + 2). The interchangability of these operators on pointers and arrays can be seen below in more detail:

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void) {
        int foo[] = { 1, 2, 3, 4, 5 };
        int *bar;
    
        bar = foo;
    
        fprintf(stdout, "%i\n", foo[2]);
        fprintf(stdout, "%i\n", *(foo + 2));
        fprintf(stdout, "%i\n", bar[2]);
        fprintf(stdout, "%i\n", *(bar + 2));
    
        return 0;
    }
    

All of those fprintf statements print the same result. Think about why that is.

There's one other neat thing you can do with pointers to elements of arrays. It is true that usually the most convenient way to access one-dimensional arrays via pointers is by pointers to their first elements. But one can actually set the pointer to the address of another element somewhere later in the array. Pointer arithmetic done to this pointer will be relative to where that pointer points. Example:

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void) {
        int foo[] = { 0, 1, 2, 3, 4, 5, 6, 7 };
        int *bar = &foo[3];
    
        fprintf(stdout, "%i\n", *bar);
        fprintf(stdout, "%i\n", *(bar + 1));
        fprintf(stdout, "%i\n", *(bar + 2));
    
        return 0;
    }
    

And this will print out:

    3
    4
    5
    

...because instead of setting the value of bar to the address of foo's first element, I set it to its third. All pointer arithmetic was of course relative to that address.


True Pointers to Arrays

So far, we have been dealing with pointers to the elements of one-dimensional arrays, not pointers to arrays. You might wonder what the method is for accessing members of an array with two or more dimensions. See this example below to demonstrate the inconvenience of using a pointer to char to access the strings in a two-dimensional array of chars:

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    void foo(char *bar);
    
    int main(void) {
        char baz[3][7] = { "simeon", "bar", "kochba" };
    
        foo(&baz[0][0]);
    
        return 0;
    }
    
    void foo(char *bar) {
        fprintf(stdout, "%s\n", bar);
        fprintf(stdout, "%s\n", bar + 7);
        fprintf(stdout, "%s\n", bar + 14);
    }
    

You, as a novice, should find the above example a doozy and probably have a few questions right off the bat. I'll take it step by step.

In the program's allocation of memory for the two-dimensional array baz in main, let's say that the memory in addresses 500-520 is set aside. The memory would look like this:

Address Value
500 s
501 i
502 m
503 e
504 o
505 n
506 \0
507 b
508 a
509 r
510 \0
511 junk
512 junk
513 junk
514 k
515 o
516 c
517 h
518 b
519 a
520 \0

The array baz, conceptually, has two dimensions, although the memory allocated for it is still sequential. Seven bytes have been allocated for each string, and there are three strings. Note that bar only takes up four of its allotted bytes (three characters plus an null to end it), because the second dimension needs to be seven bytes, to fit "simeon" and "kochba". Hence, the last three bytes allocated for the second string are uninitialized, and contain random junk data. Here's another way of looking at it, not in terms of addresses but instead looking at it from the abstract perspective of array dimensions in C:

  0 1 2 3 4 5 6
0 s i m e o n \0
1 b a r \0 junk junk junk
2 k o c h b a \0

Having said that, let's examine the nature of the call to function foo. Notice that it is not enough to simply type the name of the array as the argument; I wrote &baz[0][0]. Why? The equivalence of pointers and arrays is still in effect. But the first element of char baz[3][7] is not char; accordingly, the expression baz will not decay into a pointer to char The first element of char baz[3][7] is an array of seven chars. Therefore, the expression baz will become a pointer to an array of chars with seven elements. So, instead, I must type &baz[0][0]. Since the value of the expression baz[0][0] is char, prepending the ampersand of course gives us a pointer to char.

Now see those calls to fprintf in function foo. Since the variable bar is a pointer to char, pointer arithmetic will skip over individual chars for each value of one added to it. This method of printing individual strings in an array is inconvenient to say the least. To print the second string, it is necessary to add 7 to bar, because the beginning of it is in address 507. Likewise, for the third, it is necessary to add 14 to access the right data.

So, essentially, we can see that using a pointer to char accesses the data of this two-dimensional array bar in an inconvenient manner. There should be a better way! And there is...

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    void foo(char (*bar)[7]);
    
    int main(void) {
        char baz[][] = { "simeon", "bar", "kochba" };
    
        foo(baz);
    
        return 0;
    }
    
    void foo(char (*bar)[7]) {
        fprintf(stdout, "%s\n", *bar);
        fprintf(stdout, "%s\n", *(bar + 1));
        fprintf(stdout, "%s\n", *(bar + 2));
    }
    

We are aware that an array's name will always decay into a pointer to its first element. In the case, for example, of an array of chars, the name will decay into a pointer to char. So, logically, the array baz, being passed to the function foo in the above example will decay into a pointer to its first element. And its first element is an array of chars that is seven long. Hence this: char (*bar)[7]. Here, since the first element of baz is an array, we have a true pointer to an array.

Now, to the details of dereferencing a pointer to an array such as in the example. First, dereference of a pointer to an array will yield an array. This array, in turn, will decay into a pointer to its first element, leaving us in the example fprintf statements in function foo with a pointer to char as the third argument for each call. It is a two-stage process. Second, pointer arithmetic, instead of skipping over the space that an individual int, double, or char might take up, skips over a whole array's worth of space, according to what type the array is and how many elements are in it. In this case, it goes over 7 bytes because there are 7 chars. In a way, you can imagine dereferencing this pointers to arrays as replacing the statements with equivalent arrays as you see below:

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    void foo(char (*bar)[7]);
    
    int main(void) {
        char baz[][] = { "simeon", "bar", "kochba" };
    
        foo(baz);
    
        return 0;
    }
    
    void foo(char (*bar)[7]) {
        char eqv_array_0[7] = "simeon";
        char eqv_array_1[7] = "bar";
        char eqv_array_2[7] = "kochba";
    
        fprintf(stdout, "%s\n", *bar);
        fprintf(stdout, "%s\n", eqv_array_0);
        
        fprintf(stdout, "%s\n", *(bar + 1));
        fprintf(stdout, "%s\n", eqv_array_1);
        
        fprintf(stdout, "%s\n", *(bar + 2));
        fprintf(stdout, "%s\n", eqv_array_2);
    }
    

You will notice, of course, that the value expected as the third argument for each call to fprintf is a pointer to char. With eqv_array_0, eqv_array_1, and eqv_array_2, they decay into pointers to chars. But with the pointer bar, the process is two steps. First, bar is dereferenced with any necessary pointer arithmetic. The return value of that expression is an array of chars with seven elements, which then decays into a pointer to char.

Note also, that pointers to arrays can do the relative pointing as described earlier:

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    void foo(char (*bar)[7]);
    
    int main(void) {
        char baz[][] = { "simeon", "bar", "kochba" };
    
        foo(&baz[1]);
    
        return 0;
    }
    
    void foo(char (*bar)[7]) {
        fprintf(stdout, "%s\n", *bar);
        fprintf(stdout, "%s\n", *(bar + 1));
        fprintf(stdout, "%s\n", *(bar + 2)); /* Error!! */
    }
    

This points bar to the second individial in baz

And when I run it:

    bar
    kochba
    úÿ¿èùÿ¿\
    
    

Since baz starts with the second element of bar, only the strings bar and kochba are printed, and because of the offset, junk prints out after these strings. This is because the program is stepping beyond the bounds of the array in the last fprintf; it's skipping over to an array that isn't there.

Now, I will show one more example of pointers to arrays with a three-dimensional array to drive the syntax and concepts home.

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    void foo(int (*bar)[3][4]);
    
    int main(void) {
        int baz[][][] = {
         { 
          { 0, 1, 2, 3 },
          { 4, 5, 6, 7 },
          { 8, 9, 10, 11 }
         }, {
          { 12, 13, 14, 15 },
          { 16, 17, 18, 19 },
          { 20, 21, 22, 23 }
         } 
        };
    
        foo(baz);
    
        return 0;
    }
    
    void foo(int (*bar)[3][4]) {
        int (*buzz)[4];
    
        buzz = *(bar + 1);
    
        fprintf(stdout, "%i\n", *(*(buzz + 2) + 2));
    }
    

This is probably confusing. I will step through it very thoroughly.

So, in the beginning, we start with this array, baz. It has three-dimensions, so, abstractly, we can think of it being like a cuboid shape. Of course, though, the memory allocated for it will still be linear.

Address Value
8000 0
8002 1
8004 2
8006 3
8008 4
8010 5
8012 6
8014 7
8016 8
8018 9
8020 10
8022 11
8024 12
8026 13
8028 14
8030 15
8032 16
8034 17
8036 18
8038 19
8040 20
8042 21
8044 22
8046 23

The memory could look like that, for example.

Now when we pass the array baz to foo, baz will decay into a pointer to the first element of this three-dimensional array of ints. This first element is a two-dimensional array whose first dimension is 3 and whose second is 4. Note the syntax for foo's function argument: int (*bar)[3][4].

int (*buzz)[4]; is defined in the function baz. What is this? A pointer to a one-dimensional array of ints whose dimension is four.

Next, buzz is given a value: buzz = *(bar + 1);. bar is a pointer to a two-dimensional array of ints whose first dimension is 3, and second, 4. Adding one to it with pointer arithmetic and dereferencing it will yield the second row of the array baz. bar is a pointer to a two-dimensional array of ints, and it points at the first two-dimensional array of ints in baz. Adding 1 will skip to the next two-dimensional array of ints in baz (the one that starts with 12), and will be yielded upon dereference. This yielded array will decay into a pointer to its first element, which is a one-dimension array of four ints. And buzz is that type.

Now, to the final statement of fprintf: fprintf(stdout, "%i\n", *(*(buzz + 2) + 2));. The third argument is evaluated inside-out. So, we start with *(buzz + 2). Since the program knows that the dimension of the array to which buzz points is four, pointer arithmetic skips over two units of four ints, which works out to 16 bytes, since each int is two bytes. So when this is dereferenced, the third one-dimensional array in this two-dimensional array is yielded. So, imagine *(buzz + 2) is replaced with a one-dimensional array of ints with four members. Here is another equivalency example:

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    void foo(int (*bar)[3][4]);
    
    int main(void) {
        int baz[][][] = {
         { 
          { 0, 1, 2, 3 },
          { 4, 5, 6, 7 },
          { 8, 9, 10, 11 }
         }, {
          { 12, 13, 14, 15 },
          { 16, 17, 18, 19 },
          { 20, 21, 22, 23 }
         } 
        };
    
        foo(baz);
    
        return 0;
    }
    
    void foo(int (*bar)[3][4]) {
        int (*buzz)[4];
        int eqv_array[4] = { 20, 21, 22, 23 };
    
        buzz = *(bar + 1);
    
        fprintf(stdout, "%i\n", *(eqv_array + 2));
    }
    

Since the yielded value of the expression *(buzz + 2) is an array of ints with four elements, there of course will be the decay into a pointer that we are very familiar with again. It will become a pointer to int, to which two is added in pointer arithmetic. The program skips over the amount of memory that holds two ints, and dereferences the address, leaving us with a final value o int 22. Compile and run the program to see.

Now, consider the method people would usually use to access the elements of a multi-dimensional array: just using the subscript operator like so: kochba[1][2][1]. Now we can really understand what goes on there:

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void) {
        int kochba[][][] = { { { 0, 1 }, { 2, 3 }, { 4, 5 } },
                                { { 6, 7 }, { 8, 9 }, { 10, 11 } } };
    
        fprintf(stdout, "%i\n", kochba[1][2][1]);
    
        return 0;
    }
    

To obtain the int value for fprintf's argument, these events happen:

  1. kochba decays into a pointer to the first element of the three-dimensional array of ints. This is thus a pointer two-dimensional array of ints, whose first dimension is 3 and whose second is 2.
  2. The subscript operator adds to this value and dereferences it. This yields the second row of the array.
  3. The array value yielded decays into a pointer to its first element. This first element is an individual array of ints that has a dimension of two. Therefore, we have a pointer to an array of two ints.
  4. The subscript operator adds two to this, skipping over two arrays of two ints. This value is dereferenced, yielding a single array of two ints.
  5. This array value once more decays into a pointer to its first element. Since the first element of this array is a int it is a pointer to int.
  6. Once again, this value is added to with pointer arithmetic; the space of two ints is skipped over, and this value dereferenced.
  7. And of course, this yields the int value: 11.

The subscript operator adds clarity to programs when used on pointers or arrays, though it is usually associated with the latter. For example, the example using the three-dimensional array (example13.c in the tarball) can be rewritten like so:

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    void foo(int (*bar)[3][4]);
    
    int main(void) {
        int baz[][][] = {
         { 
          { 0, 1, 2, 3 },
          { 4, 5, 6, 7 },
          { 8, 9, 10, 11 }
         }, {
          { 12, 13, 14, 15 },
          { 16, 17, 18, 19 },
          { 20, 21, 22, 23 }
         } 
        };
    
        foo(baz);
    
        return 0;
    }
    
    void foo(int (*bar)[3][4]) {
        int (*buzz)[4];
    
        buzz = bar[1];
    
        fprintf(stdout, "%i\n", buzz[2][2]);
    }
    

This is obviously so much easier to read. I will, however, continue to use the pointer arithmetic and dereference form, such as *(bar + i) as opposed to bar[i], just to drive the critical distinction between pointers and arrays home. When you are out there writing real C programs yourself, you will probably prefer the subscript method.


Pointers to Pointers

This is another useful aspect of C. Here is a first example of their use:

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    void foo(char **bar, int size);
    
    int main(void) {
        char array_0[] = "simeon";
        char array_1[] = "bar";
        char array_2[] = "kochba";
    
        char *ptr_array[3];
    
        ptr_array[0] = array_0;
        ptr_array[1] = array_1;
        ptr_array[2] = array_2;
    
        foo(ptr_array, 2);
    
        return 0;
    }
    
    void foo(char **bar, int size) {
        int i;
    
        for (i = 0; i <= size; i++) {
            fprintf(stdout, "%s\n", *(bar + i));
        }
    }
    

ptr_array is an array of pointers to char. In the lines following its definition, the address of the first elements of each array of chars is assigned to an element of ptr_array by decay of the names into pointers to their first elements. A pointer to the first element of ptr_array is passed to foo as its first argument. And what is the type of a pointer to the first element of ptr_array? Pointer to pointer to char The second argument is the length of ptr_array, starting with 0. In foo, each element of ptr_array is accessed through the pointer bar. This table may help:

Variable or Element(s) Thereof Address(es) Value(s)
First Element of array_0 800 s
First Element of array_1 807 b
First Element of array_2 811 k
Elements of ptr_array 818, 822, 826 800, 807, 811
bar 830 818

It will be assumed that the size of each pointer is four bytes. That's what I have, it may not be what you have.

So anyway, a for loop in foo prints each of the arrays of char defined in main, and here's how: In the first iteration of the for loop, i is 0. So nothing is added to bar, whose value is 818, the address of the first pointer in ptr_array. So the data of address 818 is retrieved by the dereference operator: 800 (the address of the first element of array_0). Since the value yielded by dereferencing a pointer to pointer to char is a pointer to char and fprintf expects a pointer to char as its third argument, this works and simeon is printed out as a result. In the next iteration, the same happens, except that since i is 1, pointer arithmetic skips over to and yields the pointer to char adjacent to the first in ptr_array; that is, it dereferences the address 822, yielding 807. Of course fprintf prints out bar. And likewise for the last iteration.

How is this actually useful?? Why not just make a two-dimensional array of chars? Well, you already know that the size of the second dimension of a two-dimensional array of chars must be the size of the greatest string in the array; look at this table from True Pointers to Arrays again. This is wasted space:

  0 1 2 3 4 5 6
0 s i m e o n \0
1 b a r \0 junk junk junk
2 k o c h b a \0

If I use the pointer to pointer to char scheme, each pointer to char in ptr_array that is referenced by the pointer to pointer to char, bar, points to the first char of arrays of different sizes. There is no wasted space, since the dimensions of the arrays are not co-dependent. This is one way this approach can be useful.

Another way you will use pointers to pointers is pragmatic. You have to use them if you want your program to be able to use arguments. This is particularly important if you are programming in a Unix environment, where even recent X Window System programs can take arguments.

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(int argc, char **argv) {
        int i;
    
        for (i = 0; i < argc; i++) {
            fprintf(stdout, "%s\n", *(argv + i));
        }
    
        return 0;
    }
    

Arguments are given to a C program in the form of arguments to main, the int argc, which is the number of arguments, starting at zero, and pointer to pointer to char argv. argv is a pointer to the first element of an array of pointers to char not unlike the one used in the previous example, and it represents the arguments to the program. The first pointer to char in the array to which argv points points at the first element of a string containing the name of the program. The next one points to the first element of a string containing the the first argument, and so forth. These are created when the program starts. This program simply prints out each argument given to the program.

So for example, if I am running this in the bash shell, a command line interpreter available on many operating systems, and I do:

gcc -o arguments_demo example20.c
./arguments_demo simon bar kochba led the jewish struggle for
    independence

This is what prints:

    ./arguments_demo
    simon
    bar
    kochba
    led
    the
    jewish
    struggle
    for
    independence
    

Also, I can access indvidual chars in each argument string like so:

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(int argc, char **argv) {
        int i;
        int arg_len = strlen(*(argv + 1));
    
        for (i = 0; i < arg_len; i++) {
            fprintf(stdout, "%c", *(*(argv + 1) + i));
        }
    
        fprintf(stdout, "\n");
    
        return 0;
    }
    

What this does is prints out each character in the first argument to the program, then prints a newline character. In each iteration of the for loop, argv, plus one, is decremented, yielding a pointer to char. i is added to this, and decremented, yielding an individual char which is then printed. arg_len is the length of the string, so that for knows when to stop. It is obtained from strlen. This is how strlen is defined: size_t strlen(const char *s);. Since it accepts a pointer to char as its argument, to get the length of the first argument to the program, I add 1 to argv and dereference it. This yields a pointer to char and strlen takes it from there.


malloc and the sizeof Operator

And now, we come to the topic of dynamic allocation. This is one more compelling reasons to use pointers.

What is dynamic allocation? Dynamic allocation is when you can allocate and delete segments of memory, of arbitrary size whenever you want. This memory will not be reallocated unless it is freed, so it is safely reserved. Well, of arbitrary size, except in the case that you run out of memory.

malloc and free are the most important functions for allocating and freeing dynamic memory. malloc sets aside memory; free returns that memory to the pool of memory available for allocation. When you are done with a given piece of memory alloted by malloc, you must free it. Otherwise, a memory leak will occur. When a block of memory is not freed by free, it remains reserved even after the program ends, and will thusly not be usable later. Big memory leaks that accumulate with successive runs of a program may actually even tie up all the system memory.

Now that I've done a synopsis of what dynamic allocation is, I can move on to how to use malloc and free in a C program. First, a very trivial example, which I will explain in detail:

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void) {
        int *foo = malloc(3 * sizeof(int));
    
        *foo = 42;
        *(foo + 1) = 69;
        *(foo + 2) = 32;
    
        fprintf(stdout, "%i %i %i\n", *foo, *(foo + 1), *(foo + 2));
    
        free(foo);
    
        return 0;
    }
    

Examining this source code, you will note what appears to be a new function, sizeof. This is actually a built-in operator in C that takes an argument, which is the type whose size in bytes it returns. This is a very essential portability feature, for, though some types like char are guaranteed to be a certain size, others vary according to the platform. In my case, it's four bytes. In the call to malloc, it is used to specify the number of bytes to allocate: 3 times the number of bytes in an int.

If you look at the definition of malloc, you will notice it returns void *. What is that? It's the void pointer. This is not to be confused with void. void * is a general pointer that points to anything, which makes it possible for malloc to allocate space for all types of pointers. It is not necessary to cast a void * pointer value when assigning it to a pointer of a specific type or, conversely, when assigning a pointer-of-specific-type value to a void pointer. This is because the cast is done implicitly by the compiler. However, because the type of pointer value in a void * is not stored, dereferencing a void pointer requires a cast to the appropriate type.

Having said that, the call to malloc can be examined in full. int *foo = malloc(3 * sizeof(int)); allocates three ints and returns a pointer to that block of memory, which is then stored in foo. The whole of this block of memory can now be accessed through foo. I will use the table tactic to visualize the memory again:

Address Value
100 42
104 69
108 32

On the lines that follow the allocation, the memory to which foo points is altered to fit the representation above. In a sense, the use of malloc above created something not unlike an array, but with the advantage of dynamic sizing. For example, the size of an array cannot be dictated at runtime by a variable like user input. This restriction does not apply to dynamically allocated memory, as we will see in the next example:

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(int argc, char **argv) {
        int size = atoi(*(argv + 1));
        int *foo = malloc(size * sizeof(int));
        int i;
    
        for (i = 0; i < size; i++) {
            *(foo + i) = i;
        }
    
        for (i= 0; i < size; i++) {
            fprintf(stdout, "%i\n", *(foo + i));
        }
    
        free(foo);
    
        return 0;
    }
    

Allocation here is truly dynamic; the first argument to the program is converted to an int by the function atoi, which is in turn supplied to malloc to determine the number of ints to which foo will point. Each int in that block is assigned a number one greater than the last, such that all the ints in the block allocated by malloc are filled. (Note that, in good C fashion, we start with 0.). Then, each int is printed out to prove the operation was a success. Compile and try it.

The final essential stage of dynamic allocation you see in both examples is the use of the free function. free, as said before, will relinquish a block of allocated memory to further use. This step is critical as unfreed memory will not be usable again until you reboot! It reduces the performance of a computer and may even cause crashes in sufficient levels; Windows in particular is prone to crashes related to memory shortages. The free function accepts a pointer to the data that was allocated; it may not even be the original pointer, it must simply point to the same address. For instance:

    /* vim:ts=4
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    void trivial_free_wrapper(void *bar);
    
    int main(int argc, char **argv) {
        int size = atoi(*(argv + 1));
        int *foo = malloc(size * sizeof(int));
        int i;
    
        for (i = 0; i < size; i++) {
            *(foo + i) = i;
        }
    
        for (i= 0; i < size; i++) {
            fprintf(stdout, "%i\n", *(foo + i));
        }
    
        trivial_free_wrapper(foo);
    
        return 0;
    }
    
    void trivial_free_wrapper(void *bar) {
        free(bar);
    
        return;
    }
    

This is, as the function says, a very trivial example. But it illustrates the point that free need not take the original pointer as its argument. This becomes important as a program becomes more complex and it is necessary to pass pointer variables, including those that point to dynamically allocated memory, through networks of functions.

Applying Pointers: Linked Lists