Addresses and pointers

[pointing-hand.jpg]

The concepts of address and pointer are fundamental in any programming language. In C, these concepts are explicit; in some other linguagens they are hidden (and represented by the more abstract concept of reference).  Learning to use pointers does not come easily; some effort and practice are required.

Table of contents:

Addresses

The RAM (= random access memory) of any computer is a sequence of bytes.  The place (first, second, third, etc.) of a byte in the sequence is the address of that byte.  If  a  is the address of a byte then  a + 1  is the address of the next byte.

Each variable of a program occupies a certain number of consecutive bytes in computer memory. A variable of type char occupies 1 byte.  A variable of type int occupies 4 bytes and a double occupies 8 bytes in many computers.  The exact number of bytes occupied by a variable is given by the sizeof operador. The value of the expression sizeof (char), for example, is 1 in all the computers and the value of the expression sizeof (int) is 4 in many computers.

Each variable (in particular, each record and each array) has an address in memory. In most computers, the address of a variable is the address of its first byte. For example, after of the declarations

char c;
int i;
struct {
   int x, y;
} point;
int v[4];

the variables could have the following addresses (the example is fictitious):

c        89421
i        89422
point    89426
v[0]     89434
v[1]     89438
v[2]     89442

The address of a variable is given by the  &  operator.  If  i  is a variable then  &i  is its address.  (Do not mistake this & for the logical and operator, which is represented by && in C.)  In the example above, &i is 89422 and &v[3] is 89446.

For example, the second argument of the library function scanf is the address of the variable that will receive the value read from the keyboard:

int i;
scanf ("%d", &i);

Exercises 1

  1. Sizes.  Compile and execute the following program:
    int main (void) {
       typedef struct {
          int day, month, year; 
       } date;
       printf ("sizeof (date) = %d\n", 
                sizeof (date));
       return EXIT_SUCCESS;
    }
    

Pointers

A pointer is a special kind of variable designed to store an address.  A pointer can have the value

NULL

which represents an invalid address.  The macro NULL is defined in the stdlib.h interface and its value is 0 (zero) on most computers.

If a pointer p stores the address of a variable i, we can say  p points to i  or  p is the address of i.  (In slightly more abstracts terms, one says that p is a reference to the variable i.)  If the value of a pointer p is not NULL then

*p

is the value of the variable pointed to by p(Do not mistake this * for the multiplication operator!)  For example, if i is a variable and p is &i then *p is the same as i.

The following figure illustrates the idea. On the left, a pointer p, stored in the address 60001, contains the address of an integer.  On the right, we see a schematic representation of the meaning of *p:

[ponteiro]

Types of pointers.  There are various types of pointers: pointers to bytes, pointers to integers, pointers to pointers to integers, pointers to structs, etc.  You must explicitly specify the type of pointer you want. To declare a pointer p to an integer, for example, write

int *p;

(There are those who prefer writing int* p.)  To declare a pointer p to a struct recd, write

struct recd *p;

A pointer r to a pointer that will point to an integer (as in the case of a matrix of integers) is declared thus:

int **r;

Examples.  Suppose that a, b, and c are integer variables. Here is a silly way of doing  c = a+b:

int *p; // p is a pointer to an integer
int *q; 
p = &a; // the value of p is the address of a
q = &b; // q points to b
c = *p + *q;

Another silly example:

int *p;  
int **r; // pointer to pointer to integer
p = &a;  // p points to a
r = &p;  // r points to p and *r points to a
c = **r + b;

Exercises 2

  1. Compile and execute the following program. (See one of the exercises above.)
    int main (void) {
       int i; int *p;
       i = 1234; p = &i;
       printf ("*p = %d\n", *p);
       printf (" p = %ld\n", (long int) p);
       printf (" p = %p\n", (void *) p);
       printf ("&p = %p\n", (void *) &p);
       return EXIT_SUCCESS;
    }
    

Application

Suppose that we need a function that will interchange the values of two integer variables, say i and j.  The function

void swap (int i, int j) { 
   int temp;
   temp = i; i = j; j = temp;
}

will not produce the desired effect, since it receives the values of the variables rather than the variables themselves.  The function receives copies of the variables and interchanges the values of these copies, while the variables themselves remain unchanged.  To obtain the desired effect, we must provide the function with the addresses of the variables:

void swap (int *p, int *q)
{
   int temp;
   temp = *p; *p = *q; *q = temp;
}

To apply this function to the variables i and j just write  swap (&i, &j)  or, if you prefer,

int *p, *q;
p = &i; q = &j;
swap (p, q);

Exercises 3

  1. Check that the interchange of the values of two variables could be obtained by means of a preprocessor macro:
    #define swap (X, Y) {int t = X; X = Y; Y = t;}
    . . . 
    swap (i, j);
    
  2. Why is following code wrong?
    void swap (int *i, int *j) {
       int *temp;
       *temp = *i; *i = *j; *j = *temp;
    }
    
  3. A pointer can be used to tell a function where it must deposit the result of its calculations. Write a function hm to convert minutes into hours-and-minutes. The function receives an integer mnts and the addresses of two integer variables, say h and m, and assigns values to these variables so that m is smaller than 60 and 60*h + m is equal to mnts.  Also write a function main to test hm.
  4. Write a function mm that takes an integer array v[0..n-1] and the addresses of two integer variables, say min and max, and deposits in these variables the values of a smallest and a largest elements of the array.  Also write a function main to test mm.

Address arithmetic

The elements of any array are stored in consecutive bytes of the computer memory.  If each element of the array occupies s bytes, the difference between the addresses of two consecutive elements is s.  But the compiler adjusts the internal details to create the illusion that the difference between the addresses of two consecutive elements is 1, regardless of the value of s.  For example, after the declaration

int *v;
v = malloc (100 * sizeof (int));

the address of the first element of the array is v, the address of the second element is v+1, the address of the third element is v+2, and so on.  If i is a variable of type int then

v + i

is the address of the (i+1)-th element of the array.  The expressions  v + i  and  &v[i]  have exactly the same value and therefore the assignments

*(v+i) = 789;
v[i] = 789;

have the same effect.  Hence, any of the two code fragments below can be used to fill in the array v:

for (i = 0; i < 100; ++i)  scanf ("%d", &v[i]);
for (i = 0; i < 100; ++i)  scanf ("%d", v + i);

All these considerations are also valid for statically allocated arrays (that is, arrays allocated before the program begins to be executed) by a declaration like

int v[100];

but in this case v is a kind of constant pointer, whose value cannot be changed.

Exercises 4

  1. Suppose that the elements of an array v are of type int and each int occupies 4 bytes on your machine. If the address of v[0] is 55000, what is the value of the expression  v + 3?
  2. Suppose that i is an integer variable and v is an array of integers. Describe, in English, the sequence of operations that the computer performs to calculate the value of the expression  &v[i + 9].
  3. Suppose that v is an array. Describe the conceptual difference between the expressions  v[3]  and  v + 3.
  4. What does the following function do?
    void print (char *v, int n) {
       char *c;
       for (c = v; c < v + n; c++)
          printf ("%c", *c);
    }
    
  5. Analyse the following program:
    void func1 (int x) {
       x = 9 * x;
    }
    
    void func2 (int v[]) {
       v[0] = 9 * v[0];
    }
    
    int main (void) {
       int x, v[2];
       x    = 111; 
       func1 (x); printf ("x: %d\n", x);
       v[0] = 111; 
       func2 (v); printf ("v[0]: %d\n", v[0]);
       return EXIT_SUCCESS;
    }
    

    The program produces the following (surprising?) output:

    x: 111
    v[0]: 999
    

    Shouldn't the values of x and v[0] be equal?

  6. Someone wrote the following code to decide whether orange comes before or after apple in the dictionary. (See the section Constant character chains in the Strings chapter.)  What is wrong?
    char *a, *b;
    a = "orange"; b = "apple";
    if (a < b)
       printf ("%s comes before %s\n", a, b);
    else
       printf ("%s comes after %s\n", a, b);
    

Questions and answers