Sunday, October 30, 2011

Pointer Notation in C Programming

Consider the declaration,
int i = 3 ;
This declaration tells the C compiler to:
(a) Reserve space in memory to hold the integer value.
(b) Associate the name i with this memory location.
(c) Store the value 3 at this location.
We may represent i’s location in memory by the following memory map.

We see that the computer has selected memory location 65524 as the place to store the value 3. The location number 65524 is not a number to be relied upon, because some other time the computer may choose a different location for storing the value 3. The important point is, i’s address in memory is a number.We can print this address number through the following program:
 
main( )
{
int i = 3 ;
printf ( "\nAddress of i = %u", &i ) ;
printf ( "\nValue of i = %d", i ) ;
}

The output of the above program would be:
Address of i = 65524
Value of i = 3

Look at the first printf( ) statement carefully. ‘&’ used in this statement is C’s ‘address of’ operator. The expression &i returns the address of the variable i, which in this case happens to be 65524. Since 65524 represents an address, there is no question of a sign being associated with it. Hence it is printed out using %u, which is a format specifier for printing an unsigned integer. We have been using the ‘&’ operator all the time in the scanf( ) statement.
The other pointer operator available in C is ‘*’, called ‘value at address’ operator. It gives the value stored at a particular address. The ‘value at address’ operator is also called ‘indirection’ operator.
Observe carefully the output of the following program:

main( )
{
int i = 3 ;
printf ( "\nAddress of i = %u", &i ) ;
printf ( "\nValue of i = %d", i ) ;
printf ( "\nValue of i = %d", *( &i ) ) ;
}

The output of the above program would be:
Address of i = 65524
Value of i = 3
Value of i = 3
 
Note that printing the value of *( &i ) is same as printing the value of i.
The expression &i gives the address of the variable i. This address can be collected in a variable, by saying,
j = &i ;
But remember that j is not an ordinary variable like any other integer variable. It is a variable that contains the address of other variable (i in this case). Since j is a variable the compiler must provide it space in the memory. Once again, the following memory map would illustrate the contents of i and j.

 As you can see, i’s value is 3 and j’s value is i’s address.
But wait, we can’t use j in a program without declaring it. And since j is a variable that contains the address of i, it is declared as,
int *j ;
This declaration tells the compiler that j will be used to store the address of an integer value. In other words j points to an integer. How do we justify the usage of * in the declaration,
int *j ;
Let us go by the meaning of *. It stands for ‘value at address’. Thus, int *j would mean, the value at the address contained in j is an int.
Here is a program that demonstrates the relationships we have been discussing.
 
main( )
{
int i = 3 ;
int *j ;
j = &i ;
printf ( "\nAddress of i = %u", &i ) ;
printf ( "\nAddress of i = %u", j ) ;
printf ( "\nAddress of j = %u", &j ) ;
printf ( "\nValue of j = %u", j ) ;
printf ( "\nValue of i = %d", i ) ;
printf ( "\nValue of i = %d", *( &i ) ) ;
printf ( "\nValue of i = %d", *j ) ;
}

The output of the above program would be:
Address of i = 65524
Address of i = 65524
Address of j = 65522
Value of j = 65524
Value of i = 3
Value of i = 3
Value of i = 3

Work through the above program carefully, taking help of the memory locations of i and j shown earlier. This program summarizes everything that we have discussed so far. If you don’t understand the program’s output, or the meanings of &i, &j, *j and *( &i ), re-read the last few pages. Everything we say about C pointers from here onwards will depend on your understanding these expressions thoroughly.
Look at the following declarations,
 
int *alpha ;
char *ch ;
float *s ;

Here, alpha, ch and s are declared as pointer variables, i.e. variables capable of holding addresses. Remember that, addresses (location nos.) are always going to be whole numbers, therefore pointers always contain whole numbers. Now we can put these two facts together and say—pointers are variables that contain addresses, and since addresses are always whole numbers, pointers would always contain whole numbers.
The declaration float *s does not mean that s is going to contain a floating-point value. What it means is, s is going to contain the address of a floating-point value. Similarly, char *ch means that ch is going to contain the address of a char value. Or in other words, the value at address stored in ch is going to be a char.
The concept of pointers can be further extended. Pointer, we know is a variable that contains address of another variable. Now this variable itself might be another pointer. Thus, we now have a pointer that contains another pointer’s address. The following example should make this point clear.
 
main( )
{
int i = 3, *j, **k ;
j = &i ;
k = &j ;
printf ( "\nAddress of i = %u", &i ) ;
printf ( "\nAddress of i = %u", j ) ;
printf ( "\nAddress of i = %u", *k ) ;
printf ( "\nAddress of j = %u", &j ) ;
printf ( "\nAddress of j = %u", k ) ;
printf ( "\nAddress of k = %u", &k ) ;
printf ( "\nValue of j = %u", j ) ;
printf ( "\nValue of k = %u", k ) ;
printf ( "\nValue of i = %d", i ) ;
printf ( "\nValue of i = %d", * ( &i ) ) ;
printf ( "\nValue of i = %d", *j ) ;
printf ( "\nValue of i = %d", **k ) ;
}

The output of the above program would be:
 
Address of i = 65524
Address of i = 65524
Address of i = 65524
Address of j = 65522
Address of j = 65522
Address of k = 65520
Value of j = 65524
Value of k = 65522
Value of i = 3
Value of i = 3
Value of i = 3
Value of i = 3
Figure would help you in tracing out how the program prints the above output.
Remember that when you run this program the addresses that get printed might turn out to be something different than the ones shown in the figure. However, with these addresses too the relationship between i, j and k can be easily established.

Observe how the variables j and k have been declared,
int i, *j, **k ;
Here, i is an ordinary int, j is a pointer to an int (often called an integer pointer), whereas k is a pointer to an integer pointer. We can extend the above program still further by creating a pointer to a pointer to an integer pointer. In principle, you would agree that likewise there could exist a pointer to a pointer to a pointer to a pointer to a pointer. There is no limit on how far can we go on extending this definition. Possibly, till the point we can comprehend it. And that point of comprehension is usually a pointer to a pointer. Beyond this one rarely requires to extend the definition of a pointer. But just in case...

No comments:

Post a Comment