1. 2.3 Introducing Arrays of Pointers : Part One Arrays of pointers may sound intimidating, however it is an important concept to learn. Work through this material slowly, and it will become second nature.

Important Announcement

Beyond this point, video lessons are not available. Further, lessons beyond this point have not been fully edited. You are welcome to proceed, but I recommend that you check back later when these lessons have been finished. At this point you might wish to consider enrolling in online classes. If you are interested in doing this, please add "carlherold" to Skype (Skype is free available at www.skype.com) to discuss this option further.

If you would like to keep up to date regarding when new lessons are completed, please subscribe to Our Reddit Page.



Lesson Transcript.


This lesson may appear a bit scary at first, but it is only because I use the word pointer about a thousand times. Take it slowly, and read through the whole lesson. This is often a difficult topic, and I have done my best to explain it thoroughly. Please let me know if any of this is unclear.

In the last lesson I showed you a situation where we had two related pointers to which we gave the names: int_pointer1 and int_pointer2. It should be apparent that if you have a situation that you are giving such names to variables or pointers in general, you should consider an array instead.

Why? Let's consider I have 5 such pointers:


    int *int_pointer1 = ...
    int *int_pointer2 = ...
    int *int_pointer3 = ...
    int *int_pointer4 = ...
    int *int_pointer5 = ...
 



Now, how would I be able to create a for loop, or any kind of loop, that could use each one? I could never do something like this:


    for (i = 1; i <= 5; i++) {
        ... int_pointer i    (ex: int_pointer1, int_pointer2, etc.)
    }
 



There is simply no way to do this. C will not be able to take a partial variable name like int_pointer and figure out how to add an extra number at the end as part of a loop. On the other hand, if I have an array like this:


    for (i = 1; i <= 5; i++) {
        ... int_pointer[i] ...    (ex: int_pointer[1], int_pointer[2], etc.)
    }
 



Now this is doable. I can easily put a number inside of brackets as an array element. So to be clear, my goal in this lesson is to figure out a way that I can use int_pointer1 and int_pointer2 as if they were elements of an array.

First, realize that they are both pointers. Therefore, I need to create an array of pointers. Without evaluating the exact syntax just yet, let's imagine how this would work. Here is a description of the array of pointers I plan to create:

    int_pointer[1] = Element [1] will be a pointer to some integer in memory.
    int_pointer[2] = Element [2] will be a pointer to a different integer in memory.

That is our goal. So, let's begin.

First of all, think of this process the same as you would any other array. If we want to create an array of three pointers, then we need somewhere in memory to put them. We have been spending a lot of time using the malloc() function, so I want to do the same here.

Remember from previous lessons that having the memory is the same thing as having the data type that will fit in that chunk of memory. For example, having 3 bytes of memory is the same thing as having an array of three characters.

From this, you should be able to figure out that we need to malloc() a portion of memory large enough to fit three pointers to type int. Why three and not two? Because it will be more instructive for this lesson. How do we know how large a space to allocate?

Well, on a 32 bit system, it is likely that each pointer is 32 bits (4 bytes) in size. However, this is not true for all systems. Therefore: We cannot use malloc() with the number 4 just because we think (or know) that the size of bytes we need is 4. That may be true on our system, but not others. In other words, we cannot ever trust that we know the size of any data type - with one exception: char.

Let me say that again, as this is very important: Whenever you allocate or write any code which depends on a size of a given data type, you must use sizeof() to get the correct value. You should never assume ints are 4 bytes, or pointers are 4 bytes, etc. Always use sizeof(). There is one exception. A char data type is always going to be one byte in size.

Now let's continue.

An array of pointers will look something like this in memory:

    ['Pointer #1']['Pointer #2']['Pointer #3']...

For this lesson, assume each "block" of the above represents 4 bytes of memory. In other words, we are assuming that a pointer takes up 4 bytes. This means that if we were to give memory addresses to each of these pointers, it would be something like this:

    B0 : ['Pointer #1']
    B4 : ['Pointer #2']
    B8 : ['Pointer #3']

Notice that each pointer starts 4 bytes later than the last one started. This is the very definition of an array. An array is a repeating collection of the same data type stored sequentially in memory one element immediately after the other.

How do you work with any array? You use a pointer. Therefore, if I am working with an array of pointers, I will need a pointer.

But a pointer to what? What will our pointer be pointing to? Will it be pointing to integers? Characters?... No, it will be pointing to.. pointers!

Why? Because each pointer is to be stored in memory sequentially at B0, B4, B8, etc. That is the very definition of an array of pointers, which is what we want to create. We therefore need some pointer which will work like this:


    pointer[0] = "point to B0";
    pointer[1] = "point to B4";
    pointer[2] = "point to B8";
 



Do not worry about syntax right now. The above is a description of what we want, not actual syntax. Just understand that we need a pointer which can point to B0, then B4, then B8. However, remember that array indexing is a shortcut for pointer offsets. Therefore, the above 3 lines could also be written like this.


    *(pointer + 0) = "point to B0"
    *(pointer + 1) = "point to B4"
    *(pointer + 2) = "point to B8"
 



Why does adding one to our pointer get us to B4? Because the idea of pointer arithmetic is that you always increment by the size of the data type you are pointing to. Now, what kind of data type will we be pointing to? A pointer!

We are assuming in this lesson that a pointer is 4 bytes in size. So the first thing we need to consider is that we need to create a pointer of the data type pointer.

How do we create a pointer in general? Like this:


    data_type *pointer ...
 



What is our data type if we want an array of int pointers? Our data type is (int *) which means "a pointer to an integer".

Therefore, what we want will be similar to this:


    (int *) *pointer ...
 



What does this mean? It means "create a pointer called *pointer". Of the data type "pointer to int".

We are creating something called *pointer which will contain a memory address. The memory address it will contain will be the memory address where a pointer resides.

The syntax I showed you above is nearly correct. Let's take out the parentheses from (int *) for the data type, and see what we get:


    (int *)  *pointer_name = ...
 



Becomes:


    int *   *pointer_name = ...
 



This is correct. We are saying to create a pointer called "pointer_name" which will point to the data type: int * which means "pointer to an integer".

So, we know that pointer_name is a pointer. We know therefore that it will contain memory addresses. What kind of memory addresses will it point to? A memory address that has a pointer.

Let's go back to our earlier example:

    B0 : ['Pointer to integer #1']
    B4 : ['Pointer to integer #2']
    B8 : ['Pointer to integer #3']

Let's consider our new pointer called pointer_name we just created. Can it point to B0? Yes. Why? Because B0 is a memory address which stores an integer pointer. It can point to B4 or B8 for the same reason. It can point to any memory address which contains a pointer to an integer.

Now, whenever we have done this before, we have used malloc() based on the size of the final array we want. This is no different, so lets create the actual array now:


    int *    *ptr_array = malloc(3*sizeof(int *));
 



int * is our data type. The second * indicates we are creating something that is a pointer to our data type (which is int *). Finally, ptr_array is the name we are giving our pointer.

Finally, how big of a space do we get in memory to use it? Assuming that sizeof(int *) returns 4 bytes, we would get 12 bytes. Remember that this sizeof() can be thought of as "size of a pointer", since all pointers will be the same size.

Now, how do we use it? Well, lets evaluate some facts.

ptr_array is a pointer. It points to B0 of the 12 bytes we just allocated. So therefore:

    ptr_array = Byte #0 (The actual memory address)
    *ptr_array = *B0 (What is *at* Byte #0)

So what is *ptr_array ? It will be the actual pointer that resides at Byte #0.

How we can use the pointer at Byte #0 is the subject of the next lesson.

When you have finished this lesson, proceed to:

  1. 2.4 Introducing Arrays of Pointers : Part Two Arrays of pointers part two.