A lot of questions I see in the C tag on StackOverflow are from beginners who have never been taught the fundamental rules of pointers. (I note that these rules apply to C# as well, though it is rare to use raw pointers in C#.) A lot of introductions to pointers get caught up on the implementation details of what pointers are for a particular compiler targeting a particular architecture, so I want to be a bit more abstract than that. So, without further ado, here are the fundamentals:
- Computers store data, called values. Integers are one kind of data.
- A value may be stored in a storage location by using the assignment operator.
- People who like jargon will use lvalue to mean a storage location and rvalue to mean the value stored there. Use those terms if you like, but I find that this jargon is unnecessary. The “l” and “r” are chosen because an lvalue goes on the left side of an assignment operator and an rvalue goes on the right side.
- A local variable is associated with a storage location. When you say …
int x; x = 123;
… then there is a storage location associated with
x, and the value 123 is stored in that location.
- A C programmer may associate a type with a storage location. In the example above,
intis associated with the location of
- There are certain program code positions in which the compiler deduces that a value is required, such as the right side of an assignment or an argument to a function call. If an expression which denotes a storage location is given at such a position then the value produced is the value stored in the storage location. For example…
int x; int y; x = 123; y = x;
xdenotes a storage location; the last line means “take the value that is in the storage location associated with
xand copy it to storage location associated with
y. Note that it does not mean “
yis to be associated with the same storage location as
- A pointer is a value.
- Let me reiterate that last point. A pointer is a value; a pointer is not a storage location. Rather, a pointer can refer to a storage location.
- A C programmer may also associated a points-to type with a pointer. For example, a “pointer to
int“. Such a type is denoted by
- The unary prefix operator
&takes as its operand an expression which denotes a storage location of type
T, and produces as its value a
T. The value of the resulting pointer is called the address of the storage location, and the pointer refers to that storage location.
- Let me say that again, because this is fundamental: There are two ways to produce a value from a storage location of type
T. You can obtain the value of type
Twhich is stored in the storage location, or you can use the address-of operator to obtain a pointer value associated with the storage location.
- The unary prefix operator
*takes as its operand an expression whose value is a pointer to
T, and produces a storage location of type
- Let me say that again, because this is fundamental: if you have a pointer, its value is an address. Applying the
*operator to an address gives you the storage location assocated with the address. This operation is called dereferencing the pointer. You can use that storage location just as you would use any other storage location; in particular, you can store things to it, and you can obtain the value stored in it.
- I’ll say it again. The value of a pointer is an address. An address refers to a storage location. Dereferencing turns a reference to a storage location into a storage location; hence “dereferencing”.
- The contents of an uninitialized storage location are implementation-defined, and it is undefined what happens when you use such a value.
- “Undefined” means anything can legally happen. Working normally is “anything”. Crashing is “anything”. The program is under no obligation whatsoever to have any particular behaviour.
- In particular, the value stored in an uninitialized storage location of pointer type is an address that need not be associated with any particular valid storage location; it is implementation-defined what happens if you dereference such a value.
A commenter points out that it’s not entirely clear from this description of the rules why we’d use pointers at all. Pointers allow you to transform a storage location into a value that can then be manipulated programmatically and then turned back into a storage location.
Those are the fundamental rules. There are more rules about arrays, pointer arithmetic, casting one pointer type to another, and so on, but the rules above are the truly fundamental ones regarding pointers. Now we can look at an example:
int x; int y; int *px; x = 123; px = &x; y = *px; *px = 456;
Walk through it. We have three local variables:
px. Each is associated with a different storage location. Each starts with an implementation-determined value. We begin by storing the value 123 in the storage location associated with
x. Then we use the address-of operator on
x. What does that produce? Well,
x denotes a storage location of type
int, so the result must be a pointer value of type pointer to
int that is equal to address of
x. We store that value in the storage location associated with
px, which is of type pointer to
So far so good.
px have values. The value stored in
123 and the value stored in
px is address of
Now we assign to the storage location associated with
y. We need a value to store. What is the value of the right-hand side? Well,
px is a pointer to
*px must be a storage location of type
int. We are using a storage location in a context where we expect a value and there is no
& operator, so this must mean to fetch the value associated with the storage location. What storage location is it?
px has value address of
*px is the storage location
x. We fetch its value –
123 – and assign that value to
Finally, we use
*px as the left side of an assignment, so we must be looking for a storage location. Since the
* operator turns a pointer into a storage location, we’ve got one. What location? The location associated with
x. So we store
What now are the values stored in each of our variables?
px is still “address of
- Storage locations hold values.
storage = valuestores the value in the storage location.
- Using a storage location in a context where a value is expected produces the stored value.
- Pointers are values.
&operator takes a storage location and produces a pointer value.
*operator takes a pointer and produces a storage location.