C++ Part I · Module 8 · Pointers and References
Addresses and What a Pointer Holds
Pointers have a reputation that this module's own description rejects. The claim the curriculum makes is that nothing here is new machinery, and it is worth taking literally, because everything needed was established three modules ago. Chapter 3.1 said a variable is a name for an object and an object is typed storage with a lifetime. Chapter 7.3 said that storage is somewhere — every example in Module 7 printed an address to prove a point about duration or scope, and you have been reading hexadecimal numbers since Chapter 6.2 without being told what they are. A pointer is simply an object whose value is one of those numbers. That is the whole definition, and this chapter's job is to make it concrete enough to reason from rather than memorise. Two measurements do most of the work. The first shows that a pointer is an object in exactly the sense Module 3 meant: it has a size, and it has an address of its own, distinct from the address it holds. The second is more surprising and settles what a pointer's type is for — a pointer to an int, a pointer to a double and a pointer to a char are all the same size, so the type cannot be about the pointer at all.
Foundation16 min readC++Part IFundamentalsPointers
Part I · Module 8 · Chapter 8.1 · Addresses and What a Pointer Holds
1. Everything This Needs Is Already Established
Three facts from three earlier modules, and the definition falls out of them.
Chapter 3.1: an object is typed storage with a lifetime, and a variable is a name for one.
Chapter 7.3: that storage is somewhere. Every object has an address, and Module 7 printed dozens of them to settle questions about duration and scope.
Chapter 3.2: an object's value is just the bits it holds, interpreted according to its type.
Put those together:
A pointer is an object whose value is the address of another object.
That is the entire definition. There is no fourth idea. What makes pointers feel harder than that is partly notation — which is Chapter 8.2's subject — and partly that the value being stored happens to refer to something else, which is a new relationship rather than new machinery.
2. The Shape of It
Chapter 3.1's figure had the same three-box shape for a name, an object and a value. This one is that chain extended by one link: a pointer is an object, its value is an address, and the address identifies another object. Reading the two figures side by side is the fastest way to see why the module blurb claims there is no new machinery here.
3. A Pointer Is an Object
The claim in §1 says "an object", and Chapter 3.1's definition of an object has consequences that are measurable. An object has a size, and it is somewhere.
#include <iostream>
int main()
{
int x = 5;
double d = 2.5;
char c = 'a';
int* p = &x;
double* pd = &d;
char* pc = &c;
std::cout << "sizeof(int) = " << sizeof(int) << " sizeof(int*) = " << sizeof(int*) << '\n';
std::cout << "sizeof(double) = " << sizeof(double) << " sizeof(double*) = " << sizeof(double*) << '\n';
std::cout << "sizeof(char) = " << sizeof(char) << " sizeof(char*) = " << sizeof(char*) << '\n';
std::cout << '\n';
std::cout << "x lives at " << static_cast<const void*>(&x) << " p holds " << static_cast<const void*>(p) << '\n';
std::cout << "p lives at " << static_cast<const void*>(&p) << '\n';
std::cout << "d lives at " << static_cast<const void*>(&d) << " pd holds " << static_cast<const void*>(pd) << '\n';
std::cout << "c lives at " << static_cast<const void*>(&c) << " pc holds " << static_cast<const void*>(pc) << '\n';
return 0;
}Measured output:
sizeof(int) = 4 sizeof(int*) = 8
sizeof(double) = 8 sizeof(double*) = 8
sizeof(char) = 1 sizeof(char*) = 8
x lives at 0x16f832888 p holds 0x16f832888
p lives at 0x16f832870
d lives at 0x16f832880 pd holds 0x16f832880
c lives at 0x16f83287f pc holds 0x16f83287fRead the second block first, because it is the one that establishes §1's claim.
p holds exactly &x. 0x16f832888 on both lines. The pointer's value is where x lives.
p has an address of its own. 0x16f832870 — a different number. p is not a label attached to x; it is a separate object, in its own storage, 24 bytes away, whose contents happen to be x's address.
That second line is what makes a pointer an object rather than a notation. It is also, incidentally, why a pointer to a pointer is not a special feature: if p is an object with an address, then something can hold that address, and nothing new has to be invented.
The usual caveat. Chapter 6.2 §3 established it and it applies to every address in this module: the hex values differ on your machine and between runs. The claim is never a particular number — it is the relationship between numbers printed by one run.
4. Every Pointer Is the Same Size
Now the first block, which is the chapter's most useful measurement.
| type | sizeof | pointer type | sizeof |
|---|---|---|---|
int | 4 | int* | 8 |
double | 8 | double* | 8 |
char | 1 | char* | 8 |
The targets are 4, 8 and 1 bytes. The pointers are all 8.
That makes sense the moment you take §1's definition seriously. A pointer holds an address. An address on this machine is 8 bytes wide, and it is 8 bytes wide whether it happens to identify one byte or a thousand. The pointer's size is a property of addresses on this platform, not of what is being pointed at.
This is implementation-defined, not guaranteed. 8 bytes is what a 64-bit target uses; a 32-bit target gives 4. Chapter 3.2 §3 made the same point about int, and Chapter 1.3 §5's grades apply: the standard requires a pointer to be able to hold an address, and says nothing about how wide that is.
So the table raises an obvious question. If int*, double* and char* all store the same kind of value in the same number of bytes, what is the type for?
5. What the Type Adds
The address alone says where. It does not say what.
A pointer's type is a promise about what lives at the address it holds.
int* p says: the 8 bytes in p are an address, and at that address there is an int. The address is the value; the type is the interpretation — which is Chapter 3.2's "an object's value is the bits, interpreted according to its type" applied one level out.
That promise is what everything in the rest of this module is built on, and three things depend on it immediately:
Reading the object. Getting the value at the address requires knowing how many bytes to read and how to interpret them — 4 bytes as a two's-complement integer, or 8 as an IEEE-754 double. The address cannot supply that. Chapter 8.2 is the operator that uses it.
Stepping to the next one. Advancing a pointer "by one" has to mean one object, which requires the object's size. For int* that is 4 bytes and for double* it is 8 — Chapter 8.4 measures exactly that, and it is the clearest demonstration that the type is doing real work on identically-sized values.
Type checking. Assigning an int* to a double* is rejected, because the two promises are different. Chapter 4.10 §5 already measured this from the cast side: static_cast refuses to turn a std::uint32_t* into a float*, and the refusal is the feature.
There is one type that deliberately makes no promise — void*, a pointer that holds an address and says nothing about what is there. It exists for interfaces that genuinely must be type-agnostic, and this chapter has already used it: static_cast<const void*>(&x) is what makes the stream print an address as a number rather than trying to interpret it. It is named here so the cast in §3 is not a mystery; using it deliberately is Part II's territory.
6. Why This Is Not New Machinery
The module blurb's claim is worth testing against what you have actually been doing.
You have been reading addresses since Chapter 6.2. Every measurement in Modules 6 and 7 printed one. Chapter 6.2 §3 proved a by-value parameter is a copy by showing two different addresses; Chapter 6.4 §2 proved a reference parameter is not by showing one address twice; Chapter 7.2 §4 proved a lifetime had ended by showing two objects at the same address; Chapter 7.3 §3 sorted four storage durations by address region.
What was missing was a type that holds one. That is the only addition. The addresses were always there, and so was everything needed to describe an object sitting at one.
So the honest summary of this chapter is that it names a thing you have been looking at for two modules. What follows from naming it is considerable — the next seven chapters — but the naming itself is small.
7. What This Chapter Deliberately Did Not Do
§3's program contains &x and int* p, which is unavoidable — a pointer cannot be demonstrated without writing one. But the two operators were used rather than explained:
&x — the address-of operator, which produces the address of an object.
* — in int* p this is part of the type, not an operation. The same symbol is also an operator that reads the object at an address, and keeping those two uses apart is most of Chapter 8.2.
Chapter 8.2 is the next chapter precisely because it is short: two operators, each doing one thing, and the frozen index's own three-line example is the whole of it.
8. What to Actually Do
Hold the definition, not a metaphor. "An object whose value is the address of another object" is more useful than "it points at something", because it tells you a pointer has a size, a lifetime and an address of its own.
Expect every pointer type to be the same size on a given platform, and expect that size to be implementation-defined.
Read a pointer's type as a promise about what lives at the address — that is what distinguishes int* from double* when the stored value is the same kind of thing.
Print static_cast<const void*>(p) when you want to see an address. Without it, a char* would be printed as text.
Remember what a pointer's correctness depends on. Not just its own value, but whether an object still lives there — Chapter 8.8.
9. Mistakes Worth Making Once
Thinking a pointer is a name or a label. Measured: p has its own address, 24 bytes from x's.
Expecting sizeof(int*) to relate to sizeof(int). Measured: 4 against 8, and char* is 8 while char is 1.
Assuming 8 bytes is guaranteed. It is implementation-defined; a 32-bit target gives 4.
Thinking the type is about the pointer's size. All pointer types here are the same size; the type is about what lives at the address.
Printing a char* to see its value. It is interpreted as text. static_cast<const void*> is how you see the address.
Treating a pointer's value as self-contained. It refers to something whose existence it does not control — the chapter's callout, and Chapter 8.8's subject.
Expecting int* to convert to double*. The promises differ; Chapter 4.10 §5 measured static_cast refusing it.
10. Interview Relevance
This is one of the five Critical chapters in the module and the question is usually the opener.
"What is a pointer?" The answer that gets credit is a definition rather than a metaphor: an object whose value is the address of another object. Adding that it therefore has its own size and its own address — and that a pointer to a pointer needs no new machinery as a result — shows you hold the definition rather than a picture.
"Why is sizeof(char*) the same as sizeof(double*)?" Because both hold an address, and an address is one width on a given platform regardless of what it identifies. Then the follow-up the question is really after: so what is the type for? A promise about what lives there, which is what makes dereferencing and pointer arithmetic possible at all.
"How big is a pointer?" "Eight bytes on a 64-bit platform, and implementation-defined in general" is the complete answer. "Eight" alone invites the follow-up.
11. What You Should Be Able to Say Now
- A pointer is an object whose value is the address of another object. Nothing beyond Chapters 3.1, 3.2 and 7.3 is needed to say that.
- It is an object in Chapter 3.1's sense, so it has a size, a lifetime and an address of its own.
- Measured:
pholds0x16f832888, which is exactly&x— andpitself lives at0x16f832870. - That second fact is why a pointer to a pointer needs no new feature.
- Measured:
sizeof(int*),sizeof(double*)andsizeof(char*)are all 8, while the targets are 4, 8 and 1. - So a pointer's size is a property of addresses on this platform, not of the target — and 8 is implementation-defined.
- The type is a promise about what lives at the address — Chapter 3.2's bits-plus-interpretation, one level out.
- That promise is what makes reading the object, stepping to the next one and type checking possible.
- Chapter 4.10 §5 already measured the type checking:
static_castrefusesuint32_t*tofloat*. void*holds an address and promises nothing;static_cast<const void*>is what prints an address as a number.- The addresses were always there — Chapters 6.2, 6.4, 7.2 and 7.3 all argued from them. The new thing is a type that holds one.
- The hard part is not the notation. A pointer's value refers to something whose existence it does not control.
- That is why Chapter 7.2 came first and why this module has five Critical chapters.
- The hex values move between runs; the relationships do not.
12. Exercises
Exercise 1 — state the definition. Define a pointer in one sentence without using the words "points", "arrow" or "refers". Then name the three earlier chapters your definition depends on.
Exercise 2 — predict the sizes. On a 64-bit platform, give sizeof for each: (a) char (b) char* (c) double (d) double* (e) int**. Say which of your answers are guaranteed by the standard.
Exercise 3 — read the output. From §3's measured output, say which two numbers are equal, which number is unique to the pointer, and what each of those two facts establishes.
Exercise 4 — why not a label? A colleague says int* p = &x; makes p "another name for x". Give the measurement that disproves it and say which chapter describes the construct they are actually thinking of.
Exercise 5 — what the type is for. Given that int* and double* are the same size and hold the same kind of value, give three things the type makes possible and name the chapter that covers each.
Exercise 6 — the printing cast. Explain why std::cout << pc prints text when pc is a char*, and why static_cast<const void*>(pc) does not.
Exercise 7 — what is genuinely new. The module blurb says nothing here is new machinery. List three places in Modules 6 and 7 where you already reasoned about addresses, then state the one thing this chapter actually added.
13. Summary
- A pointer is an object whose value is the address of another object — assembled from Chapters 3.1, 3.2 and 7.3.
- It is an object: it has a size, a lifetime, and an address of its own.
- Measured:
pheld&xexactly, andplived 24 bytes away. - That is why a pointer to a pointer is not a new feature.
- Measured: every pointer type was 8 bytes while targets were 4, 8 and 1 — the size is about addresses, and it is implementation-defined.
- The type is a promise about what lives there, enabling dereference, arithmetic and type checking.
void*promises nothing and is whatstatic_cast<const void*>uses to print an address.- The addresses were always there; the new thing is a type that holds one.
- The hard part is that a pointer's value refers to something whose existence it does not control.
- Hex moves between runs; relationships hold.
Two operators were used in this chapter and neither was explained. One produces an address from an object and the other produces an object from an address, which makes them exact inverses — and the symbol for the second one is also the symbol that appears in a pointer's type, doing something completely different. The next chapter separates those two uses, which is most of what makes the notation confusing, and then there is very little left to say: the frozen curriculum's own example for it is three lines long, and the third line changes a variable that is never mentioned on it.
Standards & specifications
- Governing standard
- ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)
Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 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.
