6.1. Basics of pointers

A pointer is a variable that stores the computer’s memory address of some other data.

One of the main reasons why we have pointers is to simulate “call-by-reference” to functions. All arguments to a function are passed by value, meaning that the function can only return a single value, and changes made to the arguments of the function are lost when the function returns. By using pointers, we can pass the memory address of variables to the function. Having the memory address or a pointer to some data, the function can modify the contents of memory and thus effectively pass much more than just a single value back to the calling function.

6.1.1. Two pointer operators

*

This operator is used in two ways related to pointers. It is used to declare pointer variables and to dereference a pointer.

  • Note that when we declare a pointer, we allocate memory to keep the pointer value, but not the data which the pointer points to. That data must be declared with a separate data declaration statement.
  • For a pointer to an individual variable (not an array), dereferencing the pointer can be thought of as “contents of” the memory location pointed to by the pointer. In all cases, the dereferencing operator means to remove one pointer from the consideration of the variable’s data type. So if p is a pointer to a pointer to an integer (either int **p or int p[3][4]), then the dereference of p (*p) is a pointer to an integer.
&

This is the “address of” operator. That is, it gives the memory address of whatever variable it is used with.

6.1.2. Pointer Examples

--------------------------------------------------
int *ptr, a=3;

ptr = &a;
printf("%d", *ptr );  /* 3 is printed */
--------------------------------------------------
int *ptr, a, b;

ptr = &a;
*ptr = 3;
b = *ptr;
printf( "%d\t%d\n", a, b );   /* 3   3 */
--------------------------------------------------
int i = 3, *p = &i;

printf( "%d\n", **&p );   /* *&p == p, **&p -> contents of
                              what p points to == 3       */
---------------------------------------------------------------------

Be sure that the pointer is given a valid memory address before using the * ‘contents of’ operator. That is, the line stating ptr = &a is required in the above examples.

Pointers may also be set to the special defined constant NULL. It is defined in stdio.h and can be used to let some other part of your program know that the pointer has not yet been set to point to any meaningful memory location.

6.1.3. Pointers in function arguments

void swap( int *, int * );

int main(void)
{
   int a, b;

   ...
   swap( &a, &b );
   ...
}

void swap( int *a, int *b )
{
   int temp;

   temp = *a;
   *a = *b;
   *b = temp;
}

6.1.4. Casts with pointers

  • Pointers normally point to data of a specific data type (int, float, char, double).

  • Sometimes, especially with some library function calls, we do not want to restrict a pointer to a specific data type, so we say that the pointer points to void data.

  • An example of when a void pointer is used is when one function does not know what type of data is pointed to by the pointer.

  • In the following example, the void data is cast to the data type for which the memory will be used - the same type as the pointer assigned to this memory address. Some compilers will implicitly cast the pointers, other compilers require an explicit cast. For portability, it is best to cast the void pointer.

    int *a;
    
    a = (int *)calloc(....);  /* calloc returns void * */
                       /* a points to an array of type int */
    
  • Another example of casting a void pointer comes when the quicksort (qsort) function from the standard C library is used to sort elements in an array. The qsort function can be used with any array data because the user supplies the routine to compare two elements of the array. The qsort program passes two void pointers to the comparison routine. The code for sorting an array of pointers to strings might look like as follows. Note the dereferencing and casting of the pointers.

    char *str[N];
    
    ...
    
    qsort( str, N, sizeof(char *), scmp );
    
    ...
    
    /* scmp: string compare of *p1 and *p2 */
    int scmp( const void *p1, const void *p2 )
    {
       char *v1, *v2;
    
       v1 = *(char **)p1;
       v2 = *(char **)p2;
       return strcmp(v1, v2);
    }
    

6.1.5. Pointer arithmetic

It is common to do basic addition or subtraction of pointer values. In C we can just add or subtract values from a pointer to cause the pointer to point to other data. Pointer arithmetic is most often done with arrays. When pointer arithmetic is performed, a sizeof operation is implicit in terms of the resultant memory address. If a is a pointer to an integer, then (a+i) can be thought to mean (a + sizeof(int)*i).

#include <stdio.h>

int main(void)
{
   int i=2,j=3,k=4;
   int a[5];
   int *p, *q;

   p = &k;
   printf( "i = %d\n", *(p+2));            /* i = 2 */

   q = a + 2;
   k = (int)a + sizeof(int)*2;
   printf( "q - k = %d\n", (int)q-k );     /* q - k = 0 */
   return 0;
}