C Part I · Module 7 · Pointers
What a Pointer Actually Represents
Pointers have a reputation for being the point at which C becomes difficult, and almost none of that reputation is earned by the idea itself. Every piece of it is already in your hands. Module 2 established that an object is storage at an address. Module 5 established that a call copies its arguments, so a function cannot reach a caller's object. Module 6 established that an array is a run of storage with no stored length, and that a function cannot be handed one yet. Each of those left something unfinished, and all three are finished by the same small addition: C has a type whose values are addresses. This chapter introduces nothing beyond that sentence. It spends its length making the sentence concrete, because the usual difficulty with pointers comes from being given an analogy instead of being shown the thing.
Foundation17 min readCPart IFundamentalsPointers
Part I · Module 7 · Chapter 7.1 · What a Pointer Actually Represents
1. The Address, at Last
Chapter 2.2 established that every object occupies storage beginning at an address, and then deliberately declined to show you one. Five modules have used that picture — sizeof, the call stack, array layout, bounds — without a single address appearing on screen. Here it is:
#include <stdio.h>
int main(void)
{
int count = 42;
printf("count holds %d\n", count);
printf("count lives at %p\n", (void *)&count);
printf("sizeof count %zu bytes\n", sizeof count);
return 0;
}Output from one run:
count holds 42
count lives at 0x16dcd2778
sizeof count 4 bytesThree new pieces of notation appear, and two of them are temporary scaffolding:
&count is the address-of operator. Applied to an object, it produces that object's address. Chapter 7.2 covers it properly; for now, take it as the operator that gives you where something is.
%p is printf's conversion for printing an address.
(void *) is a cast, required because %p expects a particular pointer type. Chapter 7.12 explains what void * is; until then, treat (void *) before a %p argument as part of the printing recipe.
2. A Type Whose Values Are Addresses
An address is a value. Chapter 2.1 established that values live in objects, and that an object's type decides what its bits mean. So the obvious question is whether C has a type for this kind of value.
It does:
int *location;That declares an object named location whose values are addresses of int objects. Read the declaration as: location is a pointer to int.
Once you have the type, nothing else is new — it behaves like any other object:
#include <stdio.h>
int main(void)
{
int count = 42;
int *location = &count;
printf("count holds %d\n", count);
printf("count lives at %p\n", (void *)&count);
printf("location holds %p\n", (void *)location);
printf("location itself lives at %p\n", (void *)&location);
printf("sizeof count %zu bytes\n", sizeof count);
printf("sizeof location %zu bytes\n", sizeof location);
return 0;
}Output from one run:
count holds 42
count lives at 0x16ba52778
location holds 0x16ba52778
location itself lives at 0x16ba52770
sizeof count 4 bytes
sizeof location 8 bytesFour lines of that output carry the whole definition, so read them one at a time.
Lines two and three are the same number. count lives at 0x16ba52778, and location holds 0x16ba52778. That is the definition, observable rather than asserted:
A pointer is an object whose value is the address of another object.
Line four is a different number. location itself lives somewhere — 0x16ba52770 — because location is an object too. It is declared, it occupies storage, it has an address, and you could take its address with &location, as that line does. A pointer is not a special annotation attached to count; it is an ordinary object that happens to hold a particular kind of value.
Lines five and six disagree. count is 4 bytes and location is 8. A pointer's size is a property of the pointer, not of what it points at — which makes sense once you see that what it holds is an address, and an address is the same kind of thing regardless of what is stored there.
The arrow is worth one sentence of care. There is no arrow in memory. There are two objects, and one of them holds a number that happens to be where the other one starts. Drawing it as an arrow is a reading aid, and it is a good one — but when you need to reason precisely, go back to this object holds that address.
3. Reading the Declaration
int *location;The * in a declaration is part of the type being declared. It is not the dereference operator, which Chapter 7.2 introduces and which appears in expressions rather than declarations. Beginners meet the same character doing two jobs in their first hour with pointers, and keeping them apart from the start removes most of the confusion.
The type of location is int *, pronounced pointer to int. The compiler reads the declaration as *location would be an int, which is a historical way of writing it that explains the placement of the *.
Two spacings are common and identical to the compiler:
int *location; /* the * grouped with the name */
int* location; /* the * grouped with the type */The first is what this curriculum uses, for a reason that is only visible when you declare two at once:
int *a, *b; /* two pointers, which is probably what you wanted */
int* a, b; /* a is a pointer; b is a plain int */The * attaches to the declarator, not to the type, so int* a, b; declares one pointer and one int — almost never what the author meant. Writing int *a keeps that fact visible. Better still, declare one object per line.
4. Several Pointers, One Object
Nothing restricts how many pointers hold the same address:
#include <stdio.h>
int main(void)
{
int shared = 7;
int *first = &shared;
int *second = &shared;
printf("first holds %p\n", (void *)first);
printf("second holds %p\n", (void *)second);
printf("first == second ? %d\n", first == second);
printf("first == &shared ? %d\n", first == &shared);
return 0;
}Output:
first holds 0x16f786738
second holds 0x16f786738
first == second ? 1
first == &shared ? 1The addresses vary per run; the two comparisons are 1 every time, and those are the lines to take away. Pointers can be compared with == and != exactly like other values, and two pointers compare equal when they hold the same address — that is, when they point at the same object.
This is also the first hint of why pointers are useful. shared is one object, and now three expressions refer to it: the name shared, and the values held by first and second. A function given one of those values would be able to reach shared without having a name for it, which is Chapter 7.5's subject and the answer to a limitation Module 5 could only describe.
5. The Size of a Pointer
#include <stdio.h>
int main(void)
{
char c = 'x';
int i = 1;
double d = 2.0;
char *pc = &c;
int *pi = &i;
double *pd = &d;
printf("sizeof (char) %zu sizeof (char *) %zu\n", sizeof (char), sizeof pc);
printf("sizeof (int) %zu sizeof (int *) %zu\n", sizeof (int), sizeof pi);
printf("sizeof (double) %zu sizeof (double *) %zu\n", sizeof (double), sizeof pd);
return 0;
}Output on a typical 64-bit desktop implementation:
sizeof (char) 1 sizeof (char *) 8
sizeof (int) 4 sizeof (int *) 8
sizeof (double) 8 sizeof (double *) 8The element types are 1, 4 and 8 bytes. All three pointers are 8.
A pointer's size has nothing to do with the size of what it points at. It is whatever that implementation needs to represent an address.
Be careful about the second observation, though — that all three pointers are the same size.
6. What a Pointer Is Not
Three beliefs are worth correcting immediately, because each one makes a later chapter harder.
A pointer is not the object it points at. location and count are two objects holding two different values — 42 and an address. Assigning to one does not touch the other. Chapter 7.2's dereference operator is what lets you cross from one to the other, and it is a deliberate step rather than something that happens by itself.
A pointer is not a name. Names are resolved by the compiler, as Chapter 5.6 established, and they do not exist while the program runs. A pointer is a value, computed and stored and passed around at run time, which is exactly why it can do things a name cannot.
A pointer is not inherently dangerous. It is a value of a particular type. What is dangerous is using one that does not currently identify a valid object — and that specific hazard is the subject of Chapter 7.4 and Module 9, not a property of pointers as such.
7. Why This Was Worth Waiting For
It is worth seeing what this one type unlocks, because all three debts come due in the next few chapters.
| Left unfinished | By | Finished in |
|---|---|---|
| A function cannot change a caller's object | 5.3 | 7.5 — pass its address |
| An array passed to a function loses its length | 5.3, 6.1 | 7.6, 7.7 — arrays convert to pointers |
| You cannot refer to an object computed at run time | 6.1 | this chapter — a pointer is such a reference |
Module 5 could state its limitation precisely and could not repair it. Module 6 had to keep every array inside main. Both were waiting for a value that identifies an object, and that is all a pointer is.
8. What You Should Be Able to Say Now
- An address is a value — Chapter 2.2's idea, now printable with
%p. - Addresses differ on every run; only the relationships between them are reproducible.
&objectyields that object's address. Chapter 7.2 covers it properly.int *location;declares an object whose values are addresses ofintobjects.- A pointer is an object whose value is the address of another object — shown by two printed lines matching.
- A pointer is itself an object: it has its own address, which you can take.
- A pointer's size is unrelated to the size of what it points at.
- All pointers being the same size is implementation behaviour, not a C guarantee.
- The
*in a declaration is part of the type;int *a, *b;declares two pointers andint* a, b;does not. - Pointers compare with
==; two pointers are equal when they identify the same object.
9. Exercises
Exercise 1 — read the declarations. For each, say what is declared and what type each name has.
int *p;
double *q;
char *r, *s;
char* t, u;
int v, *w;Exercise 2 — predict what matches. Without compiling, say which of these print the same number as each other, and which vary between runs.
int n = 5;
int *p = &n;
printf("%p\n", (void *)&n);
printf("%p\n", (void *)p);
printf("%p\n", (void *)&p);
printf("%d\n", n);Exercise 3 — predict the comparisons. Give the value of each, and say which are reproducible.
int a = 1, b = 1;
int *pa = &a, *pb = &b, *pa2 = &a;
pa == pa2
pa == pb
a == bExplain why the second and third differ even though a and b hold the same value.
Exercise 4 — size reasoning. On an implementation where int is 4 bytes, double is 8 and pointers are 8, give sizeof for each, and say which answers are guaranteed by C and which are implementation-defined.
int x; int *px; double y; double *py;Exercise 5 — correct the claim. A colleague says "a double * must be bigger than a char *, because a double is bigger than a char." Say what is wrong with the reasoning, what the actual rule is, and what you would write if you genuinely needed the size of a double *.
Exercise 6 — name the two objects. For int count = 42; int *location = &count;, list the objects that exist, give each one's type and value, and say which one &location is the address of.
Exercise 7 — explain the arrow. Figure 1 draws an arrow from the pointer object to the int object. Explain in two sentences what is actually stored, and why the arrow is a reading aid rather than a description of memory.
Exercise 8 — connect it forward. Chapter 5.3 showed that swap exchanged two objects that were never the caller's. Using only what this chapter establishes, explain what you would have to give swap in order for it to reach the caller's objects — without yet saying how it would use what it was given.
10. Summary
- An address is an ordinary value;
%pprints one, and&objectproduces one. - Addresses vary between runs — only equalities and differences are reproducible.
- A pointer is an object whose value is the address of another object. Nothing more.
- It is its own object, with its own address and its own size.
- A pointer's size is unrelated to its target's, and equal pointer sizes are implementation behaviour.
- In a declaration the
*belongs to the declarator:int *a, *b;. - Pointers compare with
==; equal means same object. - This one type finishes what Modules 5 and 6 had to leave open.
The definition is now in place, and one thing is conspicuously missing from it: having an address is not the same as being able to use the object it identifies. Every example in this chapter printed addresses or compared them, and not one of them read or changed the value 42 through location. That crossing — from an address to the object at that address — is a single operator, it is the exact inverse of the one that produced the address, and the next chapter is about using both of them without having to think.
Standards & specifications
- Governing standard
- ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)
Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C Programming curriculum.
