It's hard to explain what I mean in just the title so hang with me for a second. I'm learning C and trying to understand what usecase makes a pointer variable better than just a variable which you can ask for the address of. Please take this code as an example of what I mean:
#include <stdio.h>
int main() {
int x = 5;
int *x_ptr = &x;
printf("This is the value of x_ptr: %p\n", x_ptr);
printf("This is the value of &x: %p\n", &x);
return 0;
}
My output looks like this:
This is the value of x_ptr: 0x7fff1904b72c
This is the value of &x: 0x7fff1904b72c
Is there a reason I would want to use a pointer rather than &
if the resulting values are the same? I find it essentially just duplicates variables and would eventually lead to having a bunch of variables which are duplicates appended with _ptr
that just hold a memory address value. It seems incredibly redundant.
Compiling using gcc and C23. Is it perhaps an outdated convention from an older C version like C89 or C99 with sparse usecases?
Other posts I looked at and why they don't answer my question:
#include <stdio.h>
int main() {
int x = 5;
int *x_ptr = &x;
printf("This is the value of x_ptr: %p\n", x_ptr);
printf("This is the address of &x: %p\n", &x);
int y = 7;
int *y_ptr = &y;
printf("Initial value of x: %d\n", x);
printf("Initial value of y: %d\n", y);
x = 10;
*y_ptr = 14;
printf("Doubled value of x: %d\n", *x_ptr);
printf("Doubled value of y: %d\n", y);
return 0;
}
It's possible I just don't know the right terminology (As you may be able to tell from the title) and because of that haven't made the right search queries to find this answer so I apologize if that is the case. If anyone knows of a post with a good answer or other good reference relevant to the question posed I'd be thankful.
Edit: Everyone's submissions for this post have been extremely helpful and combining the practical examples from some posts with the more conceptual examples of others. Anyone stumbling onto this thread because they were as confused as I was about relevance of pointers: I recommend starting with the post I marked as answer to help conceptualize and then read the practical examples submitted by the others. I've re-read each submission several times and all of them helped cover some gap from another post.
Thank you everyone who submitted an answer and engaged in the comments!
Both John Bollinger and Eric Postpischil’s answers are true and correct, but I think something is missing here. I think there is a fundamental misunderstanding about how the computer works.
That’s okay, though. Computers are, for most people, basically magic. While we can learn some basic syntax for a programming language and the ins and outs of if
and for
statements, that does not really help with why things are the way they are. That stuff is non-obvious and must be taught to make sense.
C is a fairly minimalist language, very much closer to the machine’s instruction set than other high-level languages, and its syntax reflects that — including its syntax for managing pointers and addresses.
The bulk of this answer is therefore intended to give a more basic understanding of how computer memory works.
Let’s compare computer memory to a car park:
Each parking space has a number which uniquely identifies its location in the lot. We call that number its address. Each spot may be occupied by some kind of car, which we call the value.
The value at address 619 is “yellow jeep”.
In order to access the yellow jeep, you must go to spot 619.
There is a degree to which this metaphor breaks down, though: I can get in a car and drive it somewhere off the lot. We can do that because the car is a physical object independent of the car park.
Not so with computers. Values cannot exist without the physical memory preserving that value. That is, a memory cell is literally modified to represent a specific value. That is kind of like telling the parking spot that it must now have a yellow jeep, then watching the asphalt rise up and mold itself into a yellow jeep, then ask it to be a BMW and watch it remold into a BMW.
Don’t worry, exactly how the electrical charge is physically maintained by the memory cell is not important. What matters is that the value cannot exist independent of the memory.
Continuing with our metaphor, suppose we wanted to put on a classic car show.
The owner of the car park told us we could use spots 256 through 512, so we arranged our show to do that, carefully creating a list of which car gets which spot, with large glossy signs and maps for the public to find their way around and everything, all properly labeled with the specific number where you can find each car.
But when we get there the morning of the show, the owner tells us that he can’t do anything about the city bulldozer and excavator sitting in those spots. Instead we have to use spots 1024 through 1280. Explicatives! Our entire parking arrangement list and all our signs and everything with numbers on it is no longer valid. Car show ruined, right?
Of course not. I real life we would just use different numbered spots, and say “Put the classic Ford muscle cars in spots 1100–1130” and put up a sign with an arrow on it that says “Ford Muscle Cars this way”.
The same problem (and solution) is true of computer programming. Without some way to use different memory locations the programs we write would only work in one specific spot in memory. Even things like parameterizing subroutines would be prohibitively difficult.
It would be nice to be able to just pick up our program and move it somewhere else, just like we did with the car show.
But wait! We can!
There are two ways in which we do this:
What would be really convenient would be to just give things names.
This is exactly what happens when you have a variable named “x
”. Remember, values do not exist unless they are at some physical location in memory, so x
must exist at some location in memory.
That means that you do not have to care if x
is physically located at memory cell number 0x557b7f024004
or cell 0x7ffe5367e044
or any other addressed cell. You only need to write “x
”, and the programming language (and the OS program loader) figures it all out for you behind the scenes. Thus, when the computer loads your program in order to run it, the computer may decide that x
will be at location 0x7ffe5367e044
today, and everything works swimmingly.
Let us drill down on our vocabulary a little.
C’s syntax actually lays this out for us very nicely:
int x = 42; // type, name, value
Now for the vocabulary troubling us the most:
To get the address of an object C, use the &
(address-of) operator:
x // value == x’s value (42)
&x // value == address of x (where x is located in memory)
To declare a pointer in C, use the *
(pointer declarator) like this:
int * x_ptr = &x; // value of x_ptr is the address of x
I very much like pretty pictures to help make sense of things:
⟶ JSYK, this picture is extremely simplified, but it gets the important point across. If any pedant has a good idea about how to improve it, comment below!
Remember, an address is a value! The value itself must be stored somewhere in memory to exist! In our example, we store the address of x
in a variable named “x_ptr
”. The value of x_ptr
is the address of the variable x
.
Now we can use x_ptr
to get at x
’s value. We do that with the *
(dereference AKA indirection) operator:
*x_ptr = -7; // change x’s value
That is, x
is the same thing as *x_ptr
— both reference the same physical piece of memory. That is different than x_ptr
(without the asterisk), which references an address.
The idea of a “reference” is exactly what names are about. The true name of any memory cell is its number (or address). But we can give it a name as a convenient alias.
Fairly often, however, we don’t have a name for something! For this we need the address. That address, like any other value, is stored in a variable somewhere we can use it.
When we dereference a pointer, we obtain direct access to the addressed object — the same as if we had used the object’s name!
*
and &
are not the same thing:&
— take an object and return its address*
— take an address and obtain an objectNot to be confused with:
int *
— type declaration for a pointer to integerAs to why we would do such a thing, sometimes we just don’t know how many values we need in our program, or which value we want do stuff to.
Simple example:
#include <stdio.h>
int main(void)
{
const char * salutation = "Hello world!";
printf("%s\n", salutation);
printf("%s\n", "Hope this helps!");
return 0;
}
The pointer variable salutation
has as its value the address of the “Hello world!
” message. The message itself does not have a name, but we do have its address.
The printf()
function can then dereference the address we give it to obtain the value and print it!
And, conveniently, printf()
can accept a different address to dereference to a different value, and print it too! (This time we have neither a name nor do we have a variable holding its address. But the address value still exists. The compiler just very sneakily gives it to printf()
for us.)
$ ./a.out
Hello world!
Hope this helps!
$ █