The blog post is about one of the most important concepts in C, pointers.

Pointers
Pointers are arguably the most important and hardest concept to learn in C. To understand pointers,
you need to understand how variables are stored in memory. When you declare and initialize a variable
like int x = 5;
, what's actually happening behind the scenes is that it stores the value 5
at an address
represented by a hexadecimal number and refers to that hexadecimal number as x
. Similarly, when you store
an array like int y[3] = {1, 2, 3};
, it stores three values next to the addresses next to each other
and refers to the address of the first element of an array as y
.
Variable Name | Address | Value |
---|---|---|
x | 0x16dc6f110 | 5 |
y | 0x16dc6f118 | 1, |
2, | ||
3 |
To print the address of the variable stored, we can use %p
(where p is short for pointer) as the format specifier and &x
for displaying the address of x
. The pointer is just a variable storing the address of another variable, which can be
defined as int *z = &x
. int *
refers to the int pointer, meaning that it stores the address of an int
variable.
After declaring and initializing the pointer, the memory looks like this:
Variable Name | Address | Value |
---|---|---|
z | 0x16dc6f108 | 0x16dc6f110 |
x | 0x16dc6f110 | 5 |
y | 0x16dc6f118 | 1, |
2, | ||
3 |
When you want to retrieve a value stored in an address from a pointer, meaning if you want to
retrieve the value of x
from the pointer z
, you can use *z
, called the dereference operator.
Why Pointers?
Well, you might be wondering why pointers are useful at all. Let's look at an example where using
pointers is instrumental. Suppose you want to have a function that can swap the values of the variables
outside of the main
function. You might come up with a function like this:
void swap (int x, int y) {
int temp = x;
x = y;
y = temp;
}
int main () {
int a = 5;
int b = 10;
swap(a, b);
printf("a: %d, b: %d", a, b);
return 0;
}
However, if you run the above, you get a: 5, b: 10
. This is because if you pass variables to a function in C,
the function takes the values of the variables, not the address. Hence, calling swap(a, b)
is the same thing
as calling swap(5, 10)
. This is called call by value. Instead of doing this, we can use pointers:
void swap (int *x, int *y) {
int temp = *x;
*x = *y;
*y = temp;
}
int main () {
int a = 5;
int b = 10;
swap(&a, &b);
printf("a: %d, b: %d", a, b);
return 0;
}
Now, instead of passing variables, we are passing pointers set to the addresses of a
and b
. Then, what happens is
that swap
is appropriately using the addresses and their corresponding values with dereference operator to swap the values,
successfully printing a: 10, b: 5
. This is called call by pointer (or call by reference), and this is one of the main reasons
why pointers are so important in C.
Arrays and Pointers
We understood how pointers work for int
from the above, and we can deduce that it works the same for char
, float
, and double
.
Then, how about arrays? Let's look at an example to understand how pointers work on arrays.
void addOne (int arr[], int size) {
for (int i = 0; i < size; i++) {
arr[i] += 1;
}
}
int main () {
int xs[3] = {1,2,3};
int size = sizeof(xs)/sizeof(xs[0]);
addOne(xs, size);
printf("%d %d %d", xs[0], xs[1], xs[2]);
return 0;
}
We've seen that if we pass a variable to a function, it does not work as expected because it calls by value.
If that applied here as well, we would expect the above to not work and just print out 1 2 3
. However,
if you run the above, it prints out 2 3 4
. Why is that? It is because when we pass an array to a function,
the array will decay to a pointer and start to behave like a pointer. A pointer can also behave like an array as follows.
int xs[3] = {1,2,3};
int *p = xs;
printf("%d", p[2]); // 2
This is because arr[i]
notation is just for accessing the value of an address, i
steps away from the arr
,
which corresponds to the address of the first element of the array. As arrays store their elements next to each other,
arr[i]
can retrieve the value stored in the ith element of an array. This means even the below is legal:
int main () {
int x = 3;
int *px = &x;
printf("%d", px[1]);
return 0;
}
Even though the above pointer is pointing to x
, which stores an integer, not an array, we can use px[1]
to
access the value that happens to be stored next to x
. You can achieve the same thing as []
with
pointer arithmetic as well.
int xs[3] = {1,2,3};
int *p = xs;
printf("%d", *(p + 1)); // 2
printf("%d", *(xs + 1)); // 2
p++;
xs+=2;
printf("%d", *(p)); // 2
printf("%d", *(xs)); // 3
You can add 1
to a pointer to move to the next address and use the *
dereference operator to access that value.
As an array decays to a pointer, arrays work the same. If they behave so similarly like that, then are they the same
thing? Well, there was a reason for me to say the array decays to a pointer instead of saying they are the same.
int xs[3] = {1,2,3};
int ys[3] = {4,5,6};
int *p = xs;
// This is possible
p = ys;
// This is NOT possible
xs = ys;
While a pointer can be set to a new address anytime, an array cannot be reassigned like that. There is another important difference between array and &array pointers.
int matrix[3][5] = {
{1,2,3,4,5},
{6,7,8,9,10},
{11,12,13,14,15}
};
printf("matrix[1]+1: %d\n", *(matrix[1]+1));
printf("&matrix[1]+1: %d\n", *(&matrix[1]+1));
// matrix[1]+1: 7
// &matrix[1]+1: --The address of 12--
The result of pointer arithmetic with an aray matrix[1]+1
is different from with an &array pointer &matrix[1]+1
. After deferencing the
&array pointer, we will just get back an address storing 12
. The reason behind it is that the &array pointer is actually pointing to
the whole array instead of the first element of the array. Because of this, when we do &matrix[1]+1
, it moves to the next row of the array, instead
of moving to the next element.
In addition to that, the dereferencing did not work because the &array pointer stores the pointer to the
first element. Hence, when we dereference it again like *(*(&matrix[1]+1))
, we will get 12
. Another way of getting 12
is
to cast the type of &matrix[1]+1
as an int
pointer, like int *p = (int *) (&matrix[1]+1)
, so that we can dereference *p
.
This is an extremely important distinction to make when dealing with arrays and pointers.
Exercises
From this article, there will be an exercise section where you can test your understanding of the material introduced in the article. I highly recommend solving these questions by yourself after reading the main part of the article. You can click on each question to see its answer.
Resources
- Portfolio Courses. 2021. Introduction to Pointers | C Programming Tutorial. YouTube.
- Portfolio Courses. 2021. Pass By Reference | C Programming Tutorial YouTube.
- Portfolio Courses. 2021. Passing an Array to a Function | C Programming Tutorial YouTube.
- Portfolio Courses. 2021. Pointer Notation | C Programming Tutorial YouTube.