INTRODUCTION One feature of the C language which can t be found in some other languages is the ability to manipulate pointers. Simply stated, pointers are variables that store memory addresses. This is the topic of this module. The ability to handle pointers allows great flexibility and makes C also suitable for writing systems programs.
OBJECTIVES At the end of this module, the student should be able to: 1. explain what a pointer is; 2. use pointer operators in a program; and 3. use pointers as function parameters.
Pointers directly deal with memory storage locations and addresses. It is necessary that we have a good understanding of the memory s structure. The exact structure varies from one machine to another.
The main memory can be viewed as a sequence of storage locations. Each location is capable of storing 1 byte of data, and the locations are numbered starting from zero. 0000 0001 0002 0003 0004 0005 0006 Fig. 6.1 Conceptual View of the main memory
The size of the main memory varies. It can be as small as 4 megabytes (4*1024*1024 bytes) or as large as 64 megabytes (64*1024*1024 bytes). The number associated with each memory location is called the address of that location. When a program is run, each variable defined in the program is assigned to a unique memory location. The memory location is used to store the value of that variable.
#include <stdio.h> int x, y; float ratio; main( ) { printf( enter two integers: ); scanf( %d %d, &x, &y); ratio = x/(float) y; printf( ratio = %f\n, ratio); } In most systems, an integer value occupies 2 bytes (or 16 bits) while a float value occupies 4 bytes (2 for the mantissa and 2 for the exponent).
variables x, y, and ratio are assigned to different memory locations. All values stored in the memory are in binary. Suppose that the numbers 10 and 20 are inputted. This stores 10 in x, 40 in y, and 0.25 in ratio. In binary, 10, 40 and 0.25 are 0000000000001010, 0000000000101000, and 00010000000000001000000000000001, respectively.
These binary representations are stored in the memory locations of the variables 0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 0010 00000000 00001010 00000000 00101000 00010000 00000000 10000000 00000001 x y ratio Fig. 6.2 Byte values stored In memory locations of variables
For our purposes, we will use a simplified presentation. We ll assume that each data item occupies exactly one memory location, and we ll write values in decimal instead of binary. 0000 0001 0002 0003 0004 0005 0006 10 40 0.25 x y ratio Fig. 6.3 Simplified representation Of main memory
One of the main strengths of the C language is its ability to use memory addresses to access variables. This is done with the use of pointer variables and two unary operators: 1) the indirection operator, *, and 2) the address operator, &. A pointer variable (simply called a pointer) is a variable that can store the memory address of another variable.
The format of defining a pointer variable is: data-type *variable-name; For example, in the definition int *p; int x, y; float *t, ratio; p is a pointer which can store the memory address of an integer variable, and t is another pointer capable of storing the memory address of a float.
To obtain the memory address of a variable, we use the address operator, & Thus, the statements p = &x; t = ∶ stores the memory address of x in p, and the address of ratio in t. p is now said to point to x, while t points to ratio.
To illustrate, suppose that all the variables defined were assigned to memory locations, Then, the statement p = &x; stores 0001 in p, and the statement t = ∶ stores 0004 in t. 0000 0001 Fig 6.4. Contents of main memory showing p pointing to x and t pointing to ratio 0001 0002 0003 0004 0005 0006 0004 p x y t ratio
The indirection operator, *, is used to access the value of the variable pointed to by a pointer. For example, y = *p; sets y to whatever value stored in address 0001 (which is occupied by x, the variable pointed to by p). Also, printf( %f\n, *t); outputs the value of ratio.
We can now access a variable in 2 ways: 1) by using a pointer 2) by using the variable name itself For example, the statements: *p = *p + 2; and x = x + 2; have the same effect they increment x by 2.
Take note that a pointer can point only to a particular data type as specified in its definition. It is wrong to write t = &y; since t is a pointer to float while y is an integer variable. Also, since pointers are variables, we can assign a pointer to a pointer. So if p and q are both pointers of the same type, the statement p = q; makes p point to where q currently points.
For example, q = &x; p = q; makes both p and q point to x. Below are more examples. int x, y, *p, *q; x = 10; p = &x; /* p points to x */ q = p; /* q points to where p points */ *q = 2; /* stores 2 in x */ p = &y; /* p points to y */ *p = *q; /* stores 2 in y */ (*p)++; /* increments y by 1 */
Like ordinary variables, a pointer variable can be assigned to a pointer value only if they are of the same type. The statement q = p; is invalid if p and q are defined as int *p; float *q; There is an exception, however. C provides a generic pointer type, void *, which can be assigned with any pointer type. For example, given the following definitions, int *p, *q, x; void *k; these are valid statements: p = &x; k = (void *) p; q = (int *) k; The result is that both p and q now points to x.
When assigning pointer values to and from a generic pointer, type casts are necessary so that the pointer values are stored properly. One common mistake committed by inexperienced programmers is the use of uninitialized pointers. For example, suppose p is defined as: int *p; At the start of the program, p does not point to any variable, so it is incorrect to immediately use the expression *p. Executing a statement such as *p = 10; causes an error.
Some uses of pointers: 1) pointers are widely used in handling dynamic variables. 2) pointers are used to access local variables from other functions. Recall: local variables defined within a function cannot be used by other functions.
For example, the program #include <stdio.h> compute_circ( ) { circ = 2*3.14*radius; } compute_area( ) { area = 3.14*radius*radius; } main( ) { float radius, circ, area; printf( Input radius: ); scanf( %f, &radius); compute_circ( ); printf( circum. = %f\n, circ); compute_area( ); printf( area = %f\n, area); } will generate errors during compilation because radius, circ and area are local variables and thus cannot be used in functions other than main.
A smart way to access local variables from other functions is to use pointers. By passing memory addresses as parameters, local variables defined outside a function can be accessed using the indirection operator, *. The program below shows a modified version of the program above. It uses pointer parameters.
#include <stdio.h> compute_circ(int *r, int *c) { *c = 2*3.14*(*r); } compute_area(int *r, int *a) { *a = 3.14*(*r)*(*r); } main( ) { float radius, circ, area; printf( Input radius: ); scanf( %f, &radius); compute_circ(&radius, &circ ); printf( circum. = %f\n, circ); compute_area(&radius, &area); printf( area = %f\n, area); } Notice that the formal parameters were defined as pointers and in the function calls, memory addresses were passed as parameters. As a result, the expression *r in functions compute_circ and compute_area refer to the local variable radius defined in main. Similarly, *c and *a refer to the variables circ and area, respectively.
This is better illustrated by picturing the main memory. Let s show how the memory is updated while the program is being executed. Initially, local variables are mapped to unique memory locations, as shown below. Let s say that the user inputted 10. 0000 0001 0002 0003 0004 0005 0006 10 radius circ area
When the function call to compute_circ was made, parameters r and c are likewise mapped to some memory locations. The function call passed the memory addresses of radius and circ as parameters, and so r and c point to radius and circ, respectively. 0000 0001 0002 0003 0004 0005 0006 10 0001 0002 radius circ area r c
Inside the function, the local variables radius and circ are accessed (through pointers) as *r and *c. The same thing happens in compute_area, with radius and area accessed as *r and *a. Now, isn t that neat? We were able to access local variables from outside the function!
A commonly-used function that you will probably encounter is the swap function. The swap function is used the exchange the values of two variables and is written as: void swap(int *a, int *b) { int temp; temp = *a; *a = *b; *b = temp; } We are assuming here that the values are integers. To swap the contents of two integer variables, say x and y, we simply make the function call swap(&x, &y);
Let us take a moment to scrutinize the function. Pointer parameters are used here to access the variables defined outside the function. In the example above, *a refers to x while *b refers to y. Now, can you see why an ampersand is placed before the variable in a scanf statement? Because actually, scanf is a function. As we know, it s job is to get input from the keyboard and store it in the variable. But we have to pass the address of the variable so that scanf can access and place the value there.
In C, and in other languages as well, variables are mapped to specific locations in the main memory. Each memory location has an address, and variables can be accessed through their memory address. C provides a pointer data type and pointer-related operators that allow the access of variables through their memory addresses. This gives the programmer much flexibility and makes C appropriate for writing systems software.