To use pointers, there are two main operations involved: reference and dereference. After this, we can see how to store pointers and use them throughout programs.
&
)In C, the reference operator will give the address (also known as its location) of the variable it is attached to.
For example, if you have a variable called number
,
placing an &
before it, becoming
&number
, gives the address of
number
:
#include <stdio.h>
int main(void)
{
int number = 5;
("number = %d\n", number);
printf("&number = %p\n", &number);
printf
return 0;
}
Sample output:
number = 5
&number = 0x7fff51831fa4
*
)The dereference operator is the opposite from the reference operator, which takes the address of a variable and gives the value stored at that location.
For example, if you had an address to an integer called
number_address
, you can get the integer that the address
points to with *number_address
.
// Assume an integer pointer called number_address exists.
("number_address = %p\n", number_address);
printf("*number_address = %d\n", *number_address); printf
Sample output:
number_address = 0x7fff51831fa4
*number_address = 5
Using the reference and dereference operators is cool, but there is one more piece of the puzzle missing. How can you store an address and use it later?
For all of the data types seen before, like int
,
double
, char
, and so on, placing an asterisk
(*
) after them indicates a pointer to that respective data
type. For example, int*
represents a pointer to an
int
, and double*
represents a pointer to a
double
.
Note: Yes, this can be confusing at first. The same symbol is used for both declaring pointers and the operation for dereferencing pointers.
With that aside, let’s see a trivial example of how a pointer is created:
#include <stdio.h>
int main(void)
{
// Recall declaring an integer, then assigning it a value.
// (This could be done in one line but is done this way for the sake of demonstration.)
// Assume the address of number is 0xAD40.
int number;
= 5;
number
// Just like creating an integer, we can create an integer pointer with int*.
int* pointer_to_number;
// We can take the address of number with &number,
// then simply assign it to pointer_to_number!
= &number;
pointer_to_number
("&number = %p\n", &number);
printf("pointer_to_number = %p\n\n", pointer_to_number);
printf
// We can dereference the pointer too!
("number = %d\n", number);
printf("*pointer_to_number = %d\n", *pointer_to_number);
printf
return 0;
}
Sample output:
&number = 0xad40
pointer_to_number = 0xad40
number = 5
*pointer_to_number = 5
There is an intense and heated debate on how where the *
should go in a pointer declaration, and there are good reasons for each
choice. For these examples here, they will be written as
“int*
”, but you may see any of the following variations,
which are all valid:
int* pointer;
int *pointer;
int * pointer;
For introducing pointers, this explanation has decided to group the asterisk with the data type since it highlights the fact that the asterisk is a contributing factor to the variable’s data type.
In other situations, this can be confusing because of the following line where multiple pointers are declared at once:
int* pointer1, pointer2, pointer3;
// Or alternatively...
int *pointer1, pointer2, pointer3;
At first glance, you may think that pointer1
,
pointer2
, and pointer3
are all declared as
integer pointers, however this is false. Only pointer1
is a
pointer since it has an asterisk next to it, and pointer2
and pointer3
are actually plain integers since they don’t
have an asterisk. I know, I wish it wasn’t this way either.
To make it more clear for those occasions where you want to declare multiple pointers at once, it makes sense to group the asterisk and the variable name together:
int *pointer1, *pointer2, *pointer3;
Some people have also considered a hybrid approach, where the first
instance of *
is grouped with the data type, and the rest
are grouped with the variable name:
int* pointer1, *pointer2, *pointer3;
These are all valid, but is important as different coders prefer different styles. Any of these styles are purely for visual understanding and are treated identically once compiled and executed.
This is where the power of pointers starts to come into play! By passing pointers into functions, they can directly use and modify the values pointed to, no longer requiring you to return something!
When passing variables into functions, there are two categories of passing: passing by value and passing by reference. The main difference is simply whether or not you are passing a pointer into a function.
What we have seen up to this point is pass-by-value, where a copy of a value is passed into a function:
#include <stdio.h>
void change_value(int value)
{
= 5;
value }
int main(void)
{
int my_value = 18;
("Initial value of my_value: %d\n", my_value);
printf(my_value);
change_value("Final value of my_value: %d\n", my_value);
printf
return 0;
}
Sample output:
Initial value of my_value: 18
Final value of my_value: 18
Nothing new here so far. Notice that since only a copy of
my_value
is passed into change_value()
, the
version in main()
is not affected.
Pass-by-reference, on the other hand, is where a copy of an address is passed into a function. Let’s create a similar function to the one before, but pass a reference to a variable instead:
#include <stdio.h>
void change_value(int* value)
{
*value = 5;
}
int main(void)
{
int my_value = 18;
("Initial value of my_value: %d\n", my_value);
printf(&my_value);
change_value("Final value of my_value: %d\n", my_value);
printf
return 0;
}
Sample output:
Initial value of my_value: 18
Final value of my_value: 5
By passing the address of my_value
,
change_value()
now has direct access to the integer and can
do anything it wants with the value!
However, this is still a copy, just the copy of an address. See this example attempting to change the address a pointer points to:
#include <stdio.h>
void change_pointer(int* pointer)
{
int new_value;
= &new_value;
pointer }
int main(void)
{
int my_value = 18;
int* my_pointer = &my_value;
("Initial value of my_pointer: %p\n", my_pointer);
printf(my_pointer);
change_pointer("Final value of my_pointer: %p\n", my_pointer);
printf
return 0;
}
Sample output:
Initial value of my_pointer: 0x7ffe390613ac
Final value of my_pointer: 0x7ffe390613ac
In this case, my_pointer
will continue to point to the
same location (&my_value
) within the context of
main()
.