← Home

Pointers in C

Notes on pointers in C from various sources:

In most modern computers, main memory is divided into bytes, each capable of storing eight bits of information. Each byte has a unique address, distinguishing it from other bytes in memory. Each variable in a program occupies one or more bytes of memory.

Address and contents
Memory addresses 0 to n-1 and binary contents: 010110011 is S, etc

A memory address can be stored in a pointer variable—variables hold values just as pointers hold addresses. When we store the address of a variable i in the pointer variable p, we say that p “points to” i. A pointer stores an address, more specifically, a pointer stores the base address or initial address of a value in memory—the memory location where the first byte of the value is stored. A pointer is an arrow to the memory address where the value starts.

Pointer pointing to the address of a variable.
Pointer p pointing to the memory address of variable i. Unknown value at the address.
Pointer pointing to the address of a variable.
Pointer ptr pointing to the memory address of variable x. Value 5 at the address.

Declaring a pointer

To create a pointer:

int *p;

You declare the data type for the type of the value that the pointer may point to, then declare the pointer name preceded by an asterisk *, which makes it clear that this is a pointer declaration and not an ordinary variable declaration. The example creates a pointer variable capable of pointing to ordinary variables of type int. (As an aside, note that a pointer might also point to an area of memory that does not belong to a variable.) The asterisk * here simply tells the compiler that p is a pointer.

Examples of pointers to various types of ordinary variables:

int *p; // p may only point to integers
float *q; // q may only point to floats
char *r; // r may only point to chars

Pointer declaration with ordinary variable declaration:

int i, *p;

Initializing a pointer

To store an address in a pointer:

int i = 5; // declare ordinary variable i, and 5 to i
int *p = &i; // declare int pointer p, assign address of i to int pointer p

Or combined:

int i = 5, *p = &i;

As long as p points to i, *p is an alias for i, that is, *p has the same value as i and changing the value of *p also changes it for i. Another alias for i is *&i because accessing the content at the address of i, also expressed as *(&i), yields the value of i, and so *&i evaluates to the value of i.

Important: Note that the asterisk * is used only in the initialization. If initialize p and you then assign it another address, you simply reference that pointer without the asterisk, as in p = &z;.

p = &i;
Pointer pointing to an address with unknown content.
Pointer pointing to the address of i. Unknown value at the address.
i = 1;
Pointer pointing to the address with 1 as content.
Pointer pointing to the address address of i. Now the address holds 1.

Warning: Do not apply the indirection operator to an uninitialized pointer. If the pointer variable has not been initialized, the result will be undefined behavior.

int *p; // indirection in uninitialized pointer

Getting an address

To get the address of an ordinary variable, use the address-of operator & on an ordinary variable. If x is an ordinary variable, then &x is the address in memory of x. In int *p = &i; the address of i is copied into pointer p. With int *q = p; the address of i is also copied into q.

Getting and changing a value

To get the value at the address of a pointer, use the indirection, dereference or value-of operator * on the pointer. In other words, the indirection operator is used to access the content at an address. If p is a pointer, then *p is the value at the address stored in the p. The * operator returns the value at the address stored in the pointer.

To change the value at the address of a pointer, use the * operator on the pointer and assign a value to it.

int x = 10;
int *ptr = &x;
*ptr = 9; // x now holds 9

Applying & to a variable returns a pointer. Applying * to pointer returns a value. Changing a value at an address changes it for every pointer pointing to it.

Do not confuse: The indirection operator * and the declaration * are confusingly similar. Bear in mind that the indirection operator is used for getting the value at the address of a pointer, whereas the latter is used for first creating the pointer and is used together with a data type. If there is a datatype, * is for declaration. If there is no datatype, * gets the value at the address of the pointer.

Purpose of pointers

In C, arguments are passed by value, that is, a copy of the argument is created inside the function, the data passed in and the data used inside the function are stored in different memory locations. In passing by value, we copy the existing data. Thus, changing the data inside the function does not change it outside the function. Any changes to the value will remain in the function’s scope block.

Pointers solve this problem:

int x = 10, y = 20;

int fun(int *p1, int *p2) {
    *p1 = 100;
    *p2 = 200;
}

fun(&x, &y);
printf("x = %d, y = %d", x, y); // x and y have been changed!

We pass pointers, not values, to the function and change the values by using the indirection operator on the pointers. *p1 is the address of x with the value 10. By assigning to *p1, we change the value at that address. Remember: to change the value at the address of a pointer, use the * operator on the pointer and assign a value to it. This is called passing by reference, that is, we pass in the memory address of our data.

Instead of passing a variable x as the argument to a function, we can supply &x, a pointer to x. When the function is called, the pointer parameter p will point to the same value of variable x, that is, the pointer will be an alias for the variable. As a result, each use of *p in the body of the function will be reference the value of x, allowing you to read the value at x and modify it.

// definition
void decompose(double x, long *int_part, double *frac_part) {
    *int_part = (long) x;
    *frac_part = x - *int_part;
}

// call
decompose(3.14159, &i, &d);

Because of the & operator in &i and &d, the arguments to decompose are pointers to i and d (memory addresses, not values). When decompose is called, the value 3.14159 is copied into parameter x, a pointer to i (address) is stored in parameter int_part and a pointer to d (address) is stored in parameter frac_part.

At *int_part = (long) x; the value of x is converted to type long and stored in the address pointed to by int_part. Since int_part points to i (the argument we passed in), the assignment puts 3 in i.

The second assignment fetches the value that int_part points to (the value of i), which is 3. This value is converted to type double and subtracted from x, giving .14159, which is stored in the address that frac_part points to.

When decompose returns, the variables i and d will have the values 3 and .14159, so the changes will have persisted out of the function.