C Language Reference for Script Programmers - Pointers

Pointers

Overview                                                                                                                                                                    

what are they?
Like arrays, pointers are not a type but rather a qualification to a type. You don’t just have a pointer, you have a pointer to an int or a pointer to a char. Pointers are the biggest cause of confusion for beginners and if you are totally unfamiliar with them get ready for some headaches. Most of the confusion, I feel, comes from the way they are commonly explained and from their name. Most people are led to believe that pointers are some strange type of thing that is not at all like the other data types, when in fact they are very similar.

the value of a pointer is a hex number
Put simply a pointer is a variable that contains the address in memory where some other data is stored. That is, all data that is held in RAM has some address assigned to it, that’s how the computer knows what is what. A pointer simply stores a number that corresponds to one of those addresses. A pointer is a number! Most often pointers are described as “pointing to” some data, implying that they perform some function or are something strange. I think this type of explanation only serves to increase confusion. All pointers do is store addresses, which happen to be hexadecimal numbers.
 

One of the things that really helped me understand pointers was to stop thinking of them as pointers, rather think of them as addresses. When a function takes as an argument a pointer to a char, think of it as taking the address of a char. Instead of a function returning a pointer to a double, think of it as returning the address of a double. They are exactly the same thing, I just find the later easier to comprehend.  

how big are they?
Pointers are all the same size, 4 bytes (32 bits). It doesn’t matter if the pointer contains the address of a char or a long double (10 bytes), the pointer itself is always 4 bytes. A pointer could even point to an array of 10,000 ints and it would still only be 4 bytes (the actual data of course would be 40k).

what makes them so special?
The fact that pointers are all the same size is really important for a couple of reasons. One is Polymorphism, which we’ll get into later. The second is that pointers let you get around the limitations of C’s default “pass by value” method.

passing by value
When you pass a variable to a function as an argument, a copy of that variable is made for use locally by the function. This is referred to as “pass by value” because the “value” of the arguments are copied to the argument variables local to the function. If the variables are very large then large amounts of memory need to be shuffled around to make these copies. So to avoid this, we pass the function an address to the large amount of data rather than passing the values of the large amount of data. So in the case of the array of 10,000 ints we would only pass a 4 byte address instead of 40k worth of ints, quite a difference indeed.

passing by reference
By passing the address of a large variable we can alter that variable from within the function that it is being passed to. If we were to pass the large variable by value, a copy would be made locally and any alterations from within the function would only be made on that local copy.
 

By passing the address of a variable (passing a pointer) we are “passing by reference” rather than by value. By reference we mean address. This can be viewed as a disadvantage in that you need to take extra precautions if you don’t want the data that is passed by reference to be altered from within the function. However since C limits functions to returning only a single value this is the only way to trick a function into returning more than one value. We can pass the address of several variables and alter all of the them within the function, getting around the single return value.

So some programming nerds are probably complaining that you aren’t really passing by reference, which is true. You are still passing by value, however the value that you are passing is an address which is close enough to “true” pass by reference for me. It might help you keep it straight if you think of it as “pass by address” instead.

If you haven’t absorbed all that yet don’t worry about it. We’ll go back over all of it now with some examples. But keep in mind that the biggest cause of bugs is misuse of pointers, so even the pros have a hard time with them from time to time. So don’t get discouraged. 

Syntax                                                                                                                                                                      

declaration
to declare a variable as a pointer to a certain data type you add a * after the type name: 

int *      pint;
char *     pchar;
double *   pdbl;

this creates a pointer to an int, a pointer to a char and a pointer to a double respectively. Adding a ‘p’ to the beginning of the variable’s name helps us remember that it is a pointer, but is not required. The * after the typename in a declaration means that it is a pointer, the * has many other meanings that depend on the context as we will soon see.

assignment
So how do we make a pointer actually point to something? Remember that this means that a pointer contains the address of some other variable, that is how it “points” to it. We use the &, called the “address operator”:

int *      pint;
int        a_real_int;
pint = &a_real_int;

Here we declare two variables, a pointer to an int and an int. Then we assign the address of the int to the pointer. The last line is read “pint equals the address of a_real_int”. So the value of pint is the address of a_real_int, which might look something like: 0x00000143 (the 0x prefix indicates that this is a hex number, in base 10 it would be: 323).

It should be fairly obvious that a pointer can have it’s value changed just like any other variable. That means that a pointer need not always point to the same variable, this can be very useful. Also just as three ints can all have a value of 25, three pointers can all have a value of 0x00000008. That is more than one pointer can point to the same variable.

dereferencing
Pointers to variables are of little use if we have no way of reading or modifying the value of the variable pointed to by a pointer. At first we might want to do something like this:

pint = 35;

thinking that this will change the value of a_real_int to 35. This is not the case however, the line above will change the value of pint (which we remember is an address) to 35 or 0x00000023. That can have catastrophic results since pint will be pointing to the memory stored at address 0x00000023 which could be anything, some other program’s data or worse yet system information.

Clearly we need some way of identifying when we intend to change the value (address pointed to) of a pointer, and when we intend to change the value of the variable pointed to by the pointer. We do so with the help of our old friend the asterisk:

*pint = 35;

Yup that’s the same * we used to declare the variable as a pointer, now we’re using it to indicate that we want to access the variable pointed to by the pointer. Don’t worry if you think that seems weird, it is. As I said earlier the * has several meanings that all depend on context. In a declaration it means that the variable is a pointer. Anywhere else it indicates that the variable that follows is to be “dereferenced”. By dereferenced we mean that we want to access the variable that the pointer references (or points to). Of course * also means multiply so you need to be careful to observe the order of operations (see a C book). When in doubt surround the variable with parentheses and you’ll be fine:

a_real_int = 25 * (*pint);

The line above assigns to a_real_int the value of 25 times the value of the int that pint points to. According to our example pint points to a_real_int so the line above makes a_real_int equal 25 times it’s previous value.

passing as arguments
Let’s assume we have the function:

int  test_func(int *val1, int *val2)
{
    
*val1 = *val2;
    
return 0;
}

This function takes two arguments both of type pointer to int. The code of the function takes the value of the int pointed to by the second argument and assigns it to the value of the int pointed to by the first. We would call the function as follows:


int *      pint1, pint2;
int        number1, number2; 

// assign the addresses of the ints to the pointers:
pint1 = &number1;
pint2 = &number2;

// use pint2 to set the value of number2 to 56:
*pint2 = 56;

// call the function test_func( ) to assign the value of number2
// to number1:
test_func(pint1, pint2);
… 

You’ll notice that when we pass pint1 and pint2 to test_func we don’t use a *. The reason for this is that the function is defined as taking int *’s as it’s arguments, and both pint1 and pint2 are int *’s, therefore no dereferencing is necessary. If we had dereferenced them as in:

test_func(*pint1, *pint2);

we would be attempting to pass ints, not int *’s. This would result in a compile time error, so don’t do that.

Here’s something that might not be immediately obvious but should help you understand pointers better:


int        number1, number2;

number2 = 56;

test_func( &number1, &number2);

Can you see why this works? Remember that a pointer’s value is an address. What are we passing to the function test_func in this example? We’re using the address operator to pass the address of number1 and number2. Remember also that C passes by value, that is the variables val1 and val2 of test_func get the value of the arguments passed to the function assigned to them. Since the values being passed are addresses, and the value of pointers are addresses, they are compatible. The value of an address to an int is the same as the value of a pointer to an int (because they are the same thing), a hexadecimal address.

Converted from CHM to HTML with chm2web Pro 2.82 (unicode)