Names specified here
Name Description Notes Source Availability
ATOMIC_POINTER_LOCK_FREE Lock-free property of pointer-to-object types ? M <stdatomic.h> C11
NULL Null pointer constant L M Headers C89 C90 C95 C99 C11
void * Generic pointer type L T Native C89 C90 C95 C99 C11

Pointers to objects

Pointers are references to objects (which include variables). They can be passed around as values, be stored in variables, and be arithmetically operated on. They allow one part of a program to access a variable indirectly, i.e., without knowing its name. Indeed, a variable does not need a name if you have a pointer to it.

(The term address is usually synonymous with pointer value.)

As with any other kind of value, a pointer has a type. This type expresses not only that the value is a pointer that can reference an object, but also what type of object it references. For example, a pointer that can reference an int object has type pointer-to-int, which is written as int *. float * is the type of a value that can reference a float. Objects can be declared of these types, and therefore hold values of these types.

You normally obtain a pointer to a named object obj with the expression &obj, which can be read as address of obj. Whatever type T obj is declared as, the expression &obj has type T * or pointer-to-T, and it can be assigned to a variable of that type. For example:

double obj;
double *ptr = &obj;

ptr is now said to point to obj, allowing obj to be accessed indirectly, e.g., in a place where the name obj has no meaning, or has a different meaning. *ptr is now a synonym for obj:

*ptr = 10.0;
*ptr += 3.0;
printf("obj's value is %g\n", *ptr);

Here, we are first assigning 10 to obj, then incrementing it by 3, then printing out its current value. The * operator is said to dereference its pointer operand, making *ptr synonymous with the referenced object obj at that moment.

A pointer variable can be made to point to different objects at different times:

double a, b, c;
double *ptr;

ptr = &a;
*ptr = 1.0;

ptr = &b;
*ptr = 2.0;

ptr = &c;
*ptr = 3.0;

That would be equivalent to:

double a, b, c;

a = 1.0;

b = 2.0;

c = 3.0;

For every type T, there is a pointer-to-T type. Therefore, there is also pointer-to-pointer-to-int (int **), pointer-to-pointer-to-pointer-to-int (int ***), ad nauseum. In practice, more than two levels of indirection are extremely rare.

So far, we're not really exploiting the main utility of pointers, which is that the same piece of code could be made to operate on a different variable than obj, simply by assigning a new value to ptr. In practice, this is usually done through functions with parameters of pointer types. For example, the following idiom is used to swap the values of two variables a and b:

int a = 4, b = 9;

{
  int tmp = a;
  a = b;
  b = tmp;
}

But this piece of code is only capable of swapping the values of those two specific variables. By placing the code in a function that takes pointers to any two variables (of the right type!), we can apply it to any two such variables as often as we want:

void swap(int *p1, int *p2)
{
  int tmp = *p1;
  *p1 = *p2;
  *p2 = tmp;
}

int a, b, c, d;
swap(&a, &b);
swap(&a, &c);
swap(&c, &d);
swap(&b, &d);

Parameters of pointer type therefore allow us to write functions that consume information indirectly and/or produce it indirectly too. This indirection not only allows the function to assign to variables other than its own, but also to be passed (say) bulky structures with just a relatively lightweight address.

A pointer type can be made atomic by using the keyword _Atomic, e.g., char *_Atomic. Such a type is lock-free always if ATOMIC_POINTER_LOCK_FREE is 2, sometimes if 1, and never if 0.

Generic pointers

Pointers of different types are incompatible with each other. A value of type double * cannot be assigned to an int * variable, for example. You can coerce the conversion with a cast, but the converted value is usually useless, as information might be lost during the conversion, and attempting to interpret an area of memory as an int, when it is currently being interpreted as a double, has no guarantee of success, and no universally agreed meaning. Furthermore, different types may impose different alignment constraints on objects of those types, and so the double * need not have correct alignment for int, perhaps leading to the program aborting, either at the moment of conversion or later when the pointer is dereferenced.

The exception is the generic pointer type, whose expression void * appropriates the void keyword (as no object of that type can exist).

int var;
int *ip = &var;
void *vp = ip;
double *dp = vp;

However, although no cast is required, the other caveats about interpretation of the referenced memory and alignment constraints still apply.

You cannot dereference a generic pointer, nor apply pointer arithmetic to it.

Generic pointers are most useful in dynamic storage and with function pointers.

[ Work in progress : Say something about type punning here?]

Null pointers

A null pointer has the value 0 or (symbolically) NULL. No object ever exists where a null pointer points. NULL is defined in all of these headers:

NULL can only be used with pointer-to-object types, not pointer-to-function types. 0 can be used with either.

Implementations are permitted to define NULL as ((void *) 0), which is not strictly compatible with pointer-to-function types.

A null pointer must not be dereferenced, nor can pointer arithmetic be applied to it. Such operations have undefined behaviour.

Pointers to functions

Pointers can also reference functions. Each pointer to a function has a specific type that includes the function's parameter and return types, and the addresses of functions with distinct types are incompatible with each other. The following assignments are okay:

double sum(double a, double b);
double multiply(double a, double b);
double (*ptr)(double a, double b); // a pointer to a function

ptr = &sum;
ptr = &multiply;

The following is an error, because the type of the address of multiply is not compatible with the type of ptr:

int multiply(int a, int b);
double (*ptr)(double a, double b);

ptr = &multiply;

A pointer to a function can be dereferenced, and used to invoke the function:

int multiply(int a, int b);
double (*ptr)(double a, double b);
double v1, v2, s;

ptr = &multiply;
s = (*ptr)(v1, v2); // same as s = multiply(v1, v2)

A pointer to a function is therefore synonymous with the function's behaviour, yet it is just a value that can be stored and passed around like any other value. Pointers to functions therefore allow behaviour to be stored and passed. They are especially useful to inject behaviour into otherwise incomplete functions. qsort is a standard library function for sorting arrays. It can sort arrays of any element type, provided you tell it how to compare any two elements:

int cmp(const void *vp1, const void *vp2)
{
  const char *const *cpp1 = vp1;
  const char *const *cpp2 = vp2;

  return strcmp(*cpp1, *cpp2);
}

const char *array[] = {
  "the", "best", "laid", "plans", "of", "mice", "and", "men"
};

qsort(array, 8, sizeof array[0], &cmp);

From its arguments, qsort knows:

  1. the start of the array array,
  2. the number of elements in the array 8,
  3. the size of each element sizeof array[0], and
  4. how to compare any two elements &cmp (cmp knows how to order two strings).

qsort implements an algorithm that can sort an array, provided it knows how to sort any two elements of the array. It invokes cmp as often as necessary to fulfil this algorithm. If it needs to compare (say) elements i and j, it will invoke cmp(&array[i], &array[j]), and use the result to determine whether it should swap those elements. Simply by passing in the address of a different comparison function, qsort is able to sort arrays of any element type.


CHaR
Sitemap Supported
Site format updated 2024-06-05T22:37:07.391+0000
Data updated 1970-01-01T00:00:00.000+0000
Page updated 2022-06-17T21:43:05.000+0000