C++ Part I · Module 8 · Pointers and References
References: Binding, Aliasing and the Rules
The description of this module promised two sentences, and Chapter 8.1 spent four chapters on the first one. This chapter is the second: a reference is another name for an existing object. You have already seen that work and seen it measured — Chapter 6.4 printed the same address twice for a parameter and its argument, and Chapter 5.6 used the same mechanism for a loop variable — but both of those were specific settings, and Module 6's own description promised that references would be explained completely here. Completely turns out to mean three refusals and one reading instruction. A reference must be initialized, so there is no such thing as one that has not been bound yet. It can never afterwards be made to refer to anything else, which is the property people get wrong, because the statement that appears to rebind one compiles without complaint and quietly does something different. And there is no way to write a reference that refers to nothing, which is the property that makes it the right parameter type far more often than a pointer. The reading instruction is the one that prevents all three from feeling arbitrary: a reference is an alias, not a pointer wearing different punctuation, and one measurement settles that.
Foundation17 min readC++Part IFundamentalsReferences
Part I · Module 8 · Chapter 8.5 · References: Binding, Aliasing and the Rules
1. The Module's Second Sentence
A reference is another name for an existing object.
That is the definition, and three chapters have already relied on it:
Chapter 5.6 §5 used for (int& d : delays) and measured the array being modified where a by-value loop variable left it alone.
Chapter 6.4 §2 measured a reference parameter and its argument printing the same address, against Chapter 6.2's two different addresses for a by-value parameter.
Chapter 6.5 built the most common parameter form in C++ on it.
What those chapters did not do is state the rules. Module 6's description said references were being introduced "as a parameter mechanism and explained completely in Module 8", and this chapter is that explanation. It comes out as three things a reference cannot do, and one way of reading the declaration that makes all three obvious rather than arbitrary.
2. Binding: What Initialization Does
int a = 1;
int& r = a; // r is bound to aThe word for what happens there is binding, and it is deliberately not called assignment. r does not receive a value; r becomes another name for a.
Measured — and the address is the evidence, exactly as in Chapter 6.4:
&a = 0x16da168b8 &b = 0x16da168b4 &r = 0x16da168b8&r and &a are the same number. There is one object with two names. &r does not give you "the address of the reference" — there is no separate thing to take the address of, so &r gives the address of what r names.
That is already a sharp contrast with Chapter 8.1 §3, where &p gave a different address from p's value, because a pointer is a separate object.
3. First Refusal: It Must Be Initialized
int& r;error: declaration of reference variable 'r' requires an initializerAn error, not a warning.
This follows directly from §2. A reference is a name for an object; a declaration that does not say which object has not declared anything meaningful. There is no "unbound reference" state for it to be in.
Compare a pointer, which has one:
int* p; // legal; indeterminate value -- Chapter 3.8
int* q = nullptr; // legal; deliberately nothing -- Chapter 8.3
int& r; // ERRORChapter 3.8 established that reading an uninitialized object is undefined behaviour, and Chapter 8.3 §9 recommended nullptr precisely because an indeterminate pointer cannot even be tested. A reference removes the whole question at compile time — which is the first of the three properties that make it the safer default.
4. Second Refusal: It Can Never Be Rebound
This is the one people get wrong, and the reason is that the mistake compiles.
int& r = a;
r = b; // does this rebind r to b?It looks exactly like rebinding. It is not. Here is the program that settles it:
#include <iostream>
int main()
{
int a = 1, b = 2;
int& r = a; // r is bound to a, for good
std::cout << "before: a = " << a << ", b = " << b << ", r = " << r << '\n';
std::cout << " &a = " << static_cast<const void*>(&a)
<< " &b = " << static_cast<const void*>(&b)
<< " &r = " << static_cast<const void*>(&r) << '\n';
r = b; // does this rebind r to b, or assign b's value to a?
std::cout << "after r = b: a = " << a << ", b = " << b << ", r = " << r << '\n';
std::cout << " &r = " << static_cast<const void*>(&r) << '\n';
b = 99; // if r had rebound, r would now be 99
std::cout << "after b = 99: a = " << a << ", b = " << b << ", r = " << r << '\n';
return 0;
}Measured output, zero warnings:
before: a = 1, b = 2, r = 1
&a = 0x16da168b8 &b = 0x16da168b4 &r = 0x16da168b8
after r = b: a = 2, b = 2, r = 2
&r = 0x16da168b8
after b = 99: a = 2, b = 99, r = 2Three independent facts in that output, and it takes all three to be conclusive.
a became 2. The statement r = b; assigned b's value to a, because r is another name for a and assigning through a name assigns to the object.
&r did not change. Still 0x16da168b8, which is &a. If r had been rebound to b, &r would now be 0x16da168b4.
Changing b afterwards left r alone. b = 99 made b 99 and r stayed 2. A rebound r would have followed.
Any one of those on its own is arguable — the first is consistent with rebinding-then-reading, for instance. Together they are not.
r = b;is not a statement aboutr. It is a statement about the objectrnames. Every operation on a reference is an operation on its referent; there is nothing else for it to act on.
So there is no syntax for rebinding. None. If you need a name that can be made to refer to different objects over time, you need a pointer — and that is one of the two questions Chapter 8.6 is built on.
5. Third Refusal: It Cannot Be Null
There is no way to write a reference that refers to nothing.
int& r = nullptr;error: non-const lvalue reference to type 'int' cannot bind to a temporary of type 'std::nullptr_t'The diagnostic gives the precise reason, and it is worth reading rather than paraphrasing. nullptr is not an object sitting somewhere that a name could refer to — it is a value, so the compiler treats it as a temporary, and Chapter 6.4 §5 already measured that a non-const reference refuses to bind to one. This is that same refusal, for the same reason, and int& r = 5; is rejected with the identical message. (The diagnostic says lvalue reference, which is Chapter 13.6's vocabulary for the kind of reference this chapter is about; there is a second kind, and 13.7 is where it arrives.)
§3 established that a reference must be bound at initialization, and §4 that it can never be changed afterwards. Together those two leave no moment at which a reference refers to nothing. A reference is always bound, from its declaration to the end of its life.
What that buys is substantial, and Chapter 8.3 §7 already named it:
A reference parameter needs no null check. The "is it valid?" question that Chapter 8.3 was entirely about does not arise, because the type will not let a caller pass nothing. Chapter 8.6's measurement makes the contrast concrete — a pointer parameter can express "nothing supplied" and a reference cannot, which is a limitation in one direction and a guarantee in the other.
One honest caveat. "Never null" is a guarantee about what you can write, not an absolute guarantee about what can happen. A reference bound to an object that is later destroyed is a dangling reference, and that is a real and common bug — Chapter 7.2 §6 derived it and Chapter 8.8 enumerates the ways. So the correct statement is: a reference cannot be null, and it can dangle. Anyone who tells you references are safe has the first half.
6. Not a Pointer in Disguise
The reading instruction the frozen purpose asks for, and the measurement that justifies it.
It is tempting to model a reference as a pointer that is dereferenced automatically. That model predicts the right answer often enough to be seductive and it is wrong, and sizeof shows it:
#include <iostream>
int main()
{
int a = 5;
int& r = a;
std::cout << "sizeof(int) = " << sizeof(int) << '\n';
std::cout << "sizeof(r) = " << sizeof(r) << " (the REFERENT's size, not a pointer's)\n";
std::cout << "sizeof(int&) = " << sizeof(int&) << '\n';
std::cout << "sizeof(int*) = " << sizeof(int*) << '\n';
return 0;
}Measured output:
sizeof(int) = 4
sizeof(r) = 4 (the REFERENT's size, not a pointer's)
sizeof(int&) = 4
sizeof(int*) = 8sizeof(int&) is 4 and sizeof(int*) is 8. Asking a reference its size gives the size of the thing it names, because in the type system a reference is another name for that thing — there is no separate object to measure. Chapter 8.1 §4 measured every pointer at 8 bytes; a reference is not in that table at all.
What the standard says about implementation: nothing. It does not say a reference occupies storage and does not say it does not. A compiler may implement one as an address under the hood — and frequently does — or may make it disappear entirely, which is why Chapter 6.4's reference parameter shared the caller's address rather than adding an indirection you could observe.
That is a genuine distinction worth holding, and it is the same shape as Chapter 7.3 §8's point about durations rather than regions:
At the level of the language, a reference is a name. How a compiler arranges that is an implementation matter the standard leaves open.
The practical consequence is that reasoning about references by imagining pointers will mislead you in exactly the places this chapter is about. A pointer can be reseated, can be null, can be uninitialized, has its own address and its own size. A reference has none of those properties, and that is not a restriction placed on a pointer — it is what being a name means.
7. The Properties, Collected
Everything above, as one table. Chapter 8.6 is where this becomes a decision; here it is just the inventory.
| Pointer | Reference | |
|---|---|---|
| Must be initialized | no | yes — §3 |
| Can be reseated later | yes | no — §4 |
| Can mean "nothing" | yes (nullptr) | no — §5 |
| Has its own address | yes (Chapter 8.1 §3) | no — &r gives the referent's |
sizeof reports | the pointer, 8 | the referent, 4 |
Needs * to reach the object | yes | no — the name is the object |
| Arithmetic | yes (Chapter 8.4) | no |
| Can dangle | yes | yes — §5's caveat |
The last row is the one to remember alongside the others. Seven differences and one thing in common, and the thing in common is the dangerous one.
8. What to Actually Do
Read int& r = a; as "r is another name for a", not as "r points at a".
Expect every operation on a reference to act on its referent. Assignment, increment, taking the address — all of them.
Do not look for a way to rebind. There is none; use a pointer if you need that — Chapter 8.6.
Do not null-check a reference parameter. It cannot be null, and a check implies to a reader that it might be.
Do not model a reference as a pointer. Measured: sizeof(int&) is 4 against sizeof(int*)'s 8.
Do not reason about how a reference is implemented. The standard does not say, and your conclusions will not port.
Remember a reference can still dangle. Not null and not safe are different claims — Chapter 8.8.
9. Mistakes Worth Making Once
Writing r = b; expecting a rebind. Measured: a became 2, &r never moved, and later changing b left r alone.
Expecting a warning for that. Zero at -Wall -Wextra -Wpedantic — it is a perfectly ordinary assignment.
Declaring int& r;. error: declaration of reference variable 'r' requires an initializer.
Trying to make a reference null. There is no object called nothing.
Null-checking a reference parameter. It misleads every reader of the signature.
Expecting &r to give "the address of the reference". It gives the referent's address — there is nothing else.
Expecting sizeof(r) to be 8. Measured: 4, the referent's size.
Concluding references are safe because they cannot be null. They can dangle.
10. Interview Relevance
This is a Critical chapter and the questions are reliable.
"What is the difference between a pointer and a reference?" §7's table is the answer, and the ordering matters: must be initialized, cannot be reseated, cannot be null. If you can add that all three follow from "a reference is another name for an object" rather than listing them as rules, that is the answer the question is looking for.
"Can you reseat a reference?" No — and the reason the question is asked is that r = b; compiles. Being able to say what it actually does, and ideally how you would demonstrate it (print &r before and after, then change b), distinguishes someone who has tested it.
"Is a reference just a pointer?" No, and sizeof is the quick evidence. The complete answer adds that the standard says nothing about the implementation, so "it is a pointer under the hood" is a statement about a compiler rather than about C++.
"Are references safer than pointers?" Partly, and saying which part is the mark of experience: they cannot be null or uninitialized, and they can still dangle.
11. What You Should Be Able to Say Now
- A reference is another name for an existing object — the module blurb's second sentence.
- Initialization is called binding, and it is not assignment.
- Measured:
&rand&aare the same address — one object, two names. &rgives the referent's address; there is no separate object to take the address of.- It must be initialized —
error: declaration of reference variable 'r' requires an initializer. - It can never be rebound, and
r = b;compiles with zero warnings while doing something else. - Measured, three ways:
abecame 2,&rnever moved, and changingbafterwards leftrat 2. - Every operation on a reference acts on its referent — there is nothing else for it to act on.
- There is no syntax for rebinding. If you need it, you need a pointer — Chapter 8.6.
- It cannot be null, because §3 and §5 leave no moment at which it refers to nothing.
- So a reference parameter needs no null check — Chapter 8.3 §7's recommendation, justified.
- But it can dangle. Not null and safe are different claims — Chapter 8.8.
- Measured:
sizeof(int&)is 4 andsizeof(int*)is 8 — it is not a pointer in disguise. - The standard says nothing about how a reference is implemented, and the three refusals all follow from the one-sentence definition.
12. Exercises
Exercise 1 — predict the output. With int a = 1, b = 2; int& r = a;, give a, b and r after each step in order: (a) r = 5; (b) b = 7; (c) r = b; (d) a = 0;.
Exercise 2 — which compile? For each, say whether it compiles and give the diagnostic where it does not: (a) int& r = a; (b) int& r; (c) int& r = 5; (d) const int& r = 5; (e) int& r = nullptr;.
Exercise 3 — prove it yourself. Write the smallest program that proves r = b; does not rebind, using at least two independent pieces of evidence. Say why one alone would not be conclusive.
Exercise 4 — name the property. For each, say whether it is true of a pointer, a reference, or both: (a) has its own address (b) can be null (c) can dangle (d) needs * to reach the object (e) must be initialized (f) supports arithmetic.
Exercise 5 — the sizeof contrast. Explain why sizeof(int&) is 4 while sizeof(int*) is 8, and say what that does and does not tell you about how a compiler implements references.
Exercise 6 — derive the rules. Starting only from "a reference is another name for an existing object", derive the three refusals without consulting §3–§5.
Exercise 7 — the honest claim. A colleague says "use references instead of pointers and you cannot have memory bugs". Say exactly which bug class that removes, which it does not, and name the chapter for the one that remains.
13. Summary
- A reference is another name for an existing object — and Chapters 5.6, 6.4 and 6.5 already relied on it.
- Initialization is binding; measured,
&requals&a. - Must be initialized — an error, not a warning.
- Can never be rebound, and the mistake compiles cleanly.
- Measured three ways:
abecame 2,&rnever moved,b = 99leftrat 2. - Every operation acts on the referent; there is no rebinding syntax.
- Cannot be null, so a reference parameter needs no null check.
- But it can dangle — Chapter 8.8.
- Measured:
sizeof(int&)= 4 againstsizeof(int*)= 8 — not a pointer in disguise. - The three refusals all follow from the one-sentence definition, and the standard says nothing about implementation.
Both halves of the module's two sentences are now established, and §7's table lists eight ways they differ without saying which to use. That is deliberate, because the choice is not a matter of preference and the two rows that decide it are specific. A pointer can mean nothing and can be reseated; a reference can do neither, and in exchange it cannot be passed a null and needs no check. So the question an interface designer has to answer is not which construct they find more familiar — it is whether absence is a meaningful state for this argument, and whether anything needs to change which object is referred to. The next chapter is those two questions, and the measurement is the one thing a reference parameter genuinely cannot express.
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.
