2.1. Pointers

Sooner or later you’re going to have to come to grips with pointers, and since I’m feeling kind you’ve just earned the right to learn about them now. Read this statement carefully: pointers are a way of indirectly accessing a variable. So, you say, that sounds cool (hmm), but how do I use them and why do they work? Examine this sample code:

int a = 5;
int *b = &a; // b contains the address of a
*b+=5; // *b = *b + 5

Weird, huh? The thing which will get you is the fact that * does not mean the same thing all the way through this code. Let’s examine each line at a time, or you’ll get really confused! The first line is really self-explanatory, so starting with the second:

int *b = &a;

The * before b tells us that it is a pointer to an integer (* in declarations means pointer). The & operator gives us the address of a in this instance. If you have no idea what I’m talking about, that’s not really surprising : pointers are damn hard to get the hang of to start with. Basically, each variable is stored internally within the computer (pointers included, they are a type of variable) in what is called a VTABLE (variable table), similar to this:

AddressVariable NameValue
0×0123a5
0xABCDb0×0123

Now, address is simply where the variable is stored in the computer’s memory. So b points to a since it is a pointer to an integer (which a is) and it contains the address of a. Anyway, let’s now deal with the final line of code:

*b+=5;

There are two issues which need explaining here, the first being that x+=y is a shorthand way of writing x = x + y. The second is the meaning of * in this instance. Here *b means whatever is pointed to by b (in other words a). This is called dereferencing b since b as a pointer is referring to a indirectly and we are doing the reverse process to get the original variable. What actually happens here is that the compiler looks up the value of b and looks for the variable with that address.

2.2. Arrays Decaying to Pointers (pre-cursor to Dynamic Memory Allocation)

You may have wondered (or then again you may not) how exactly arrays work. Either way, you’re going to find out now. For example, using our earlier array, test, which if you remember was declared as follows:

char test[5];

This is where it gets a little complex. In many situations, such as when passing arrays to functions, the name of the array decays to a pointer to the first array element. So the above array would be “passed” to a function via a pointer to test[0]. This has important ramifications, especially when we consider the use of sizeof. If we work out the size of an array in the scope in which it was declared, we get its size (the number of elements multiplied by the size of an individual element), but if we pass it to a function and try to find its size using sizeof, we will end up instead calculating the size of the pointer to which the array name decayed. The important point to take away from all of this is that:

test == &test[0];

in many situations. We’ll deal with the exceptions later on. This leads us to another important point: when we write (for example)

test[1]

what we actually mean is

*(test+1)

The reasoning for this goes as follows: if test is equal to &test[0], then test[0] is equal to *test (in other words dereferencing test). If we dereference test+1, we get the next element in the array. The reason this works is because the original creators of C designed pointer addition so that incrementing a pointer by one (adding one to it) increased the pointer by the size of one element of whichever type it pointed to. So for example if we had a pointer to an integer, adding one to the pointer would increase it by the size of an integer. If you don’t quite follow this, don’t worry, it’s not necessary yet.

A further interesting point is that just as we can write test[1], we can also write 1[test]. The reasoning behind this is as follows:

1[test] == *(1+test) == *(test+1) == test[1]

2.3. Arrays of Pointers (just to be confusing)

There are occasions (for an example of this see the second parameter of the main() function right at the very beginning) where you might want to use an array of pointers. We declare an array of pointers like this:

type-specifier *array[elements];

For example, if we wanted to declare an array of ten pointers to integers, we would write the following:

int *array[10];

2.4. The Right-Left Rule

When we start getting more complicated declarations like the one above, we need some way to easily find out what’s going on. For example, of what type is array in the following example?

int *(*array)[10];

I’ll bet you don’t have a clue, because neither would I unless I knew this rule which I’m about to teach you. array is in fact a pointer to an array of ten pointers to integers. So, how do I know? Follow these steps:

1) Start with the identifier (array)
2) Read right until you encounter a bracket or reach the end of the line
3) Then read left until you encounter a bracket or reach the beginning of the line (if the latter happens you have finished)
4) Repeat steps 2 and 3 until you are done

Note that [x] is read as an array of x elements. This whole sequence is known as the right-left rule since you go first right, then left, then…you get the picture. It’s one of the most damn useful things you will ever learn in C++, so remember it well.

2.5. Dynamic Memory Allocation

Hold on to your seats! This is the interesting bit you’ve been waiting for all this time. Firstly, you need to understand something about all the variables you’ve been creating so far. They are what is known as statically-allocated, that is they were created by you at the start of the program (at “design-time”). Sometimes, you don’t know how big you want something to be at this stage, you only find out when you’re in the program (at “run-time”). This is the problem which dynamic memory allocation solves. For example, say we wished to read in a load of integers from a file (I’m not going to show you how to do that yet, but it’s a good example), then we would need an array of integers to hold those we read in. Ah, you say, that’s easy, I know exactly how to make arrays because we covered them above. Hold on Sherlock! Say you create your array of five integers (feeling very proud as you do so). Now I proceed to write six integers into a file and try and feed it into your program. Oh dear, it’s a cock-up… Even if your program doesn’t crash, the results may not be pretty. What we need is an array which we can allocate at run-time. We do this as follows:

int *dynamic_array = new int[num_elements];

This means that dynamic_array now points to the first element of a dynamically-allocated array of num_elements elements. But this is not enough for the master sleuth. If we do this too much, we’ll run out of memory. We need a way of cleaning up after ourselves. This is provided by the delete function:

delete [] dynamic_array;

Notice the use of the square brackets. This tells the compiler that we are deleting an array.

In C, memory was allocated using the commands malloc() and free(). The malloc() function had a return type of void * (this is a pointer which can point to anything whatsoever) and then had to be typecast (more on this later) to the required type. You are heading for serious trouble if you try and allocate memory with malloc() and release it with delete or allocate it with new and release it with free(). Never use these obsolete functions and you will be fine; if you’re feeling tempted to try them out, don’t.

It’s about time I showed you a complete program illustrating some of the concepts I’ve covered so far. Once again, I’ve commented it heavily, hopefully it should clarify anything you’re having difficulty understanding. Note that to use the printf() function we need to include a C header file (we use the C++ Standard header but the functions are inherited from C): we will cover the use of header files later in the course.

#include // makes printf() available – IMPORTANT!
using std::printf;
/*
Add
Adds two numbers together
and returns the result.
*/
int Add(int a, int b)
{
return (a+b);
}
/*
main
Entry and exit point for
our program.
*/
int main()
{ // Allocate a new array of three integers.
int *array = new int[3];
// Fill the array with values.
array[0] = 5;
array[1] = 10;
// Call the add function to add the two elements.
array[2] = Add(array[0], array[1]);
// Print out the three array values.
printf(”%i %i %i\n”, array[0], array[1], array[2]);
// Clear up the memory which the array is taking up.
delete [] array;
// End the program.
return 0;
}

Notice that the Add() function appeared above the main() function so that the latter knows it exists. If we had placed it below main(), we would have had to place what is known as a function prototype (also known as a declaration) above main(). A prototype for Add() would look as follows:

int Add(int a, int b);

As you can see this is exactly the same as the way we defined Add() above, but without the function body (the bit enclosed in braces) and with a semi-colon tagged onto the end. The more observant among you will notice that this is simply the way we said that functions were declared (above).

2.6. Pointers to Functions

As well as declaring pointers which can point to variables, you can also declare pointers to functions. Say you wanted to create a pointer to a function which took no parameters and returned an integer. This is *not* the correct way to do it:

int *func();

If you thought it would be this, congratulations, you have just learned how to make a function returning a pointer to an integer. As you can tell if you evaluate it using the Right-Left Rule (above), this is not what we want. Instead, we want this:

int (*func)();

As you can see, func is now a pointer to a function returning an integer, which is what we wanted. Frankly, you won’t need pointers to functions most of the time, but it just shows you what can be done. Later in the course, we’ll also cover pointers to member functions, which require a slightly different syntax.

2.A Practice Tasks

i) Write a complete program which creates a dynamic array of size 3 of integers, fills in the first two elements with values you choose, calls a function (which takes two integer parameters and returns an integer result) to multiply the two elements and stores the result in the third element. This result should then be output and the program terminated.
ii) Determine the type of variables a-c (hint: they are closely interrelated):

int (*a)();
int (*b())();
int (*(*c)())();

iii) Write the declaration of d, an array of size 10 of pointers to functions taking no parameters and returning integers. This is not easy, and using the Right-Left Rule is essential. If you can’t do it, don’t worry, it’s only an exercise.
iv) (Bonus Task) Write a program which has two functions, one which adds two integers and returns an integer result and one which does the same for subtracting. Create a function pointer to point to either of these functions, choose one which it will point to and call it by dereferencing the pointer (note this needs to be in brackets) and then calling the function pointed to. Because this is such a difficult task, I have provided the following solution. Try to understand it but don’t worry if you can’t.

#include // makes printf() available
using std::printf;
int Add(int a, int b)
{
return (a+b);
}
int Subtract(int a, int b)
{
return (a-b);
}
int main()
{
// func: pointer to function taking two ints and returning int
int (*func)(int,int);
// func points to Subtract (this is just an example here)
func = &Subtract;
// iResult is value returned from the function pFunc points to
int result = (*func)(5, 10);
printf(”%i\n”, result);
return 0;
}

Interestingly, you can simply say func = Subtract and int result = func(5, 10) since they are identical to the above forms as far as the compiler is concerned. These latter two are easier to read but tend to make you treat them as aliases to the functions rather than pointers, which has the potential to lead to nasty bugs. In general, the way shown in the code sample is the more logical way of going about things. Also, when we come to use pointers to member functions, we’ll have to do it this way in any case.

Tags: , ,

3 Responses to “Elementary C++: part 2 of 3”

  1. L3thal says:

    #include // makes printf() available
    using std::printf;

    i guess you forgot iostream or stdlib.h ?

  2. Красивый сайт мне понравился !

  3. admin says:

    Sorry, I don’t understand the language.

Leave a Reply

You must be logged in to post a comment.