Lesson - 13 Pointer

13.1 INTRODUCTION

Which feature of C do beginners find most difficult to understand? The answer is easy: pointers. Other languages have pointers but few use them so frequently as C does. And why not? It is C’s clever use of pointers that makes it the excellent language it is.

The difficulty beginners have with pointers has much to do with C’s pointer terminology than the actual concept. For instance, when a C programmer says that a certain variable is a “pointer”, what does that mean? It is hard to see how a variable can point to something, or in a certain direction.

It is hard to get a grip on pointers just by listening to programmer’s jargon. In our discussion of C pointers, therefore, we will try to avoid this difficulty by explaining pointers in terms of programming concepts we already understand. The first thing we want to do is explain the rationale of C’s pointer notation.

13.2 Pointer Notation

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.

fig-13.1

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:

void 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:

void 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.

fig-13.2

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.

void 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.

void 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

Following 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.

fig-12.5

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...

13.3 Pointer and Function

Having had the first tryst with pointers let us now get back to what we had originally set out to learn—the two types of function calls—call by value and call by reference. Arguments can generally be passed to functions in one of the two ways:

  • sending the values of the arguments

  • sending the addresses of the arguments

In the first method the ‘value’ of each of the actual arguments in the calling function is copied into corresponding formal arguments of the called function. With this method the changes made to the formal arguments in the called function have no effect on the values of actual arguments in the calling function. The following program illustrates the ‘Call by Value’.

main( )

{

int a = 10, b = 20 ;

swapv ( a, b ) ;

printf ( "\na = %d b = %d", a, b ) ;

}

swapv ( int x, int y )

{

int t ;

t = x ;

x = y ;

y = t ;

printf ( "\nx = %d y = %d", x, y ) ;

}

The output of the above program would be:

x = 20 y = 10

a = 10 b = 20

Note that values of a and b remain unchanged even after exchanging the values of x and y.

In the second method (call by reference) the addresses of actual arguments in the calling function are copied into formal arguments of the called function. This means that using these addresses we would have an access to the actual arguments and hence we would be able to manipulate them. The following program illustrates this fact.

void main( )

{

int a = 10, b = 20 ;

swapr ( &a, &b ) ;

printf ( "\na = %d b = %d", a, b ) ;

}

swapr( int *x, int *y )

{

int t ;

t = *x ;

*x = *y ;

*y = t ;

}

The output of the above program would be:

a = 20 b = 10

Note that this program manages to exchange the values of a and b using their addresses stored in x and y.

Usually in C programming we make a call by value. This means that in general you cannot alter the actual arguments. But if desired, it can always be achieved through a call by reference.

Using a call by reference intelligently we can make a function return more than one value at a time, which is not possible ordinarily. This is shown in the program given below.

void main()

{          

int radius ;

float area, perimeter ;

printf ( "\nEnter radius of a circle " ) ;

scanf ( "%d", &radius ) ;

areaperi ( radius, &area, &perimeter ) ;

printf ( "Area = %f", area ) ;

printf ( "\nPerimeter = %f", perimeter ) ;

}

areaperi ( int r, float *a, float *p )

{

*a = 3.14 * r * r ;

*p = 2 * 3.14 * r ;

}

And here is the output...

Enter radius of a circle 5

Area = 78.500000

Perimeter = 31.400000

Here, we are making a mixed call, in the sense, we are passing the value of radius but, addresses of area and perimeter. And since we are passing the addresses, any change that we make in values stored at addresses contained in the variables a and p, would make the change effective in main( ). That is why when the control returns from the function areaperi( ) we are able to output the values of area and perimeter.

Thus, we have been able to indirectly return two values from a called function, and hence, have overcome the limitation of the return statement, which can return only one value from a function at a time.

13.4 Pointer and Array

We will try to correlate the following two facts, which we have learnt before using Pointers and Array:

  • Array elements are always stored in contiguous memory locations.

  • A pointer when incremented always points to an immediately next location of its type.

Suppose we have an array num[ ] = { 24, 34, 12, 44, 56, 17 }. The following figure shows how this array is located in memory.

fig-13.3

Here is a program that prints out the memory locations in which the elements of this array are stored.

void main( )

{

int num[ ] = { 24, 34, 12, 44, 56, 17 } ;

int i ;

for ( i = 0 ; i <= 5 ; i++ )

{

 printf ( "\nelement no. %d ", i ) ;

printf ( "address = %u", &num[i] ) ;

 }

 }

The output of this program would look like this:

element no. 0 address = 65512

element no. 1 address = 65514

element no. 2 address = 65516

element no. 3 address = 65518

element no. 4 address = 65520

element no. 5 address = 65522

Note that the array elements are stored in contiguous memory locations, each element occupying two bytes, since it is an integer array. When you run this program, you may get different addresses, but what is certain is that each subsequent address would be 2 bytes (4 bytes under Windows/Linux) greater than its immediate predecessor.

Our next two programs show ways in which we can access the elements of this array.

void main()

{

int num[ ] = { 24, 34, 12, 44, 56, 17 } ;

 int i ;

for ( i = 0 ; i <= 5 ; i++ )

{

 printf ( "\naddress = %u ", &num[i] ) ;

 printf ( "element = %d", num[i] ) ;

 }

}

The output of this program would be:

address = 65514 element = 34

address = 65516 element = 12

address = 65518 element = 44

address = 65520 element = 56

address = 65522 element = 17

This method of accessing array elements by using subscripted variables is already known to us. This method has in fact been given here for easy comparison with the next method, which accesses the array elements using pointers.

Void main( )

{

int num[ ] = { 24, 34, 12, 44, 56, 17 } ;

int i, *j ;

j= &num[0] ; /* assign address of zeroth element */

for ( i = 0 ; i <= 5 ; i++ )

{

printf ( "\naddress = %u ", j ) ;

printf ( "element = %d", *j ) ; j++ ; /* increment pointer to point to next location */

}

}

The output of this program would be:

address = 65512 element = 24

address = 65514 element = 34

address = 65516 element = 12

address = 65518 element = 44

address = 65520 element = 56

address = 65522 element = 17

In this program, to begin with we have collected the base address of the array (address of the 0th element) in the variable j using the statement,

j = &num[0] ; /* assigns address 65512 to j */

When we are inside the loop for the first time, j contains the address 65512, and the value at this address is 24. These are printed using the statements,

printf ( "\naddress = %u ", j ) ; printf ( "element = %d", *j ) ;

On incrementing j it points to the next memory location of its type (that is location no. 65514). But location no. 65514 contains the second element of the array, therefore when the printf( ) statements are executed for the second time they print out the second element of the array and its address (i.e. 34 and 65514)... and so on till the last element of the array has been printed.

Obviously, a question arises as to which of the above two methods should be used when? Accessing array elements by pointers is always faster than accessing them by subscripts. However, from the point of view of convenience in programming we should observe the following:

Array elements should be accessed using pointers if the elements are to be accessed in a fixed order, say from beginning to end, or from end to beginning, or every alternate element or any such definite logic.

Instead, it would be easier to access the elements using a subscript if there is no fixed logic in accessing the elements. However, in this case also, accessing the elements by pointers would work faster than subscripts.

Last modified: Monday, 28 October 2013, 7:42 AM