Skip to content
VLSI Mentor

C++ Part I · Module 8 · Pointers and References

Address-of and Dereferencing

Chapter 8.1 defined a pointer and used two operators without explaining either, because a pointer cannot be demonstrated without writing one. This chapter explains them, and there is genuinely little to it: one operator takes an object and produces its address, the other takes an address and produces the object, and they are exact inverses of each other. Stated as a transformation on types rather than as a picture with arrows, each is a single sentence. What makes the notation confusing is not the operators but a collision in the syntax. The asterisk appears in two completely different roles that look identical on the page — in a declaration it is part of a type, saying what kind of thing is being declared, and in an expression it is an operator, doing something to a value. The same two lines of code can contain both uses, and reading them as the same thing is the single most common source of difficulty with pointers. The chapter separates them, and then the frozen curriculum's own three-line example says everything else: the third line assigns through a pointer and changes a variable whose name does not appear on it.

Foundation15 min readC++Part IFundamentalsPointers

Part I · Module 8 · Chapter 8.2 · Address-of and Dereferencing

1. Two Operators, Stated as Transformations

The frozen purpose asks you to describe in words what each one does to a value, so here they are, as transformations rather than pictures:

OperatorTakesProduces
&an object of type Tits address, of type T*
*an address of type T*the object of type T at that address

That is the whole of it. Each goes one step along Chapter 8.1's Figure 1 — & moves from the object to the address, * moves from the address to the object — and they go in opposite directions.

Reading them aloud helps more than a diagram:

&x — "the address of x".

*p — "the object that p points at", or more precisely "the object at the address p holds".

2. & — Address-of

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int x = 5;
    std::cout << static_cast<const void*>(&x);   // where x lives

&x produces a value of type int*. It is an ordinary expression producing an ordinary value — Chapter 4.1's model applies, and like any expression it has a type.

What it requires: an object. & needs something that actually occupies storage, so it needs the thing Chapter 6.4 §5's note called an lvalue — an expression naming an object that already exists. Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
error: cannot take the address of an rvalue of type 'int'

&5 is rejected for exactly the reason a non-const reference refused to bind to a literal: 5 is a value with nowhere to live, so there is no address to take. The diagnostic introduces a second term — an rvalue is, provisionally, an expression that does not name a persistent object, which is the other half of the pair Chapter 6.4 §5 deferred. Chapter 13.6 defines both properly; the working rule here is simply that & needs a named object.

What it produces is a value you can store, copy, compare and pass to a function like any other. That is Chapter 8.1's point that a pointer is an ordinary object.

3. * — Dereference

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int* p = &x;
    std::cout << *p;        // the object p points at -- prints 5

*p produces the object, not a copy of its value. That distinction is what makes the next section's example work, and it is the same distinction Chapter 6.4 drew for references: *p can be read, and it can also be assigned to.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    *p = 7;                 // assign to the object p points at

What it requires: that p actually holds the address of a live object. That is the precondition, it is not checked, and it is the whole subject of Chapters 8.3 and 8.8. For this chapter, assume it holds.

What the type does. Chapter 8.1 §5 established that a pointer's type is a promise about what lives at the address. * is the operator that collects on the promise: it needs to know how many bytes to read and how to interpret them, and the type is where that comes from. Dereferencing a void* is not allowed, because there is no promise to collect. Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
error: ISO C++ does not allow indirection on operand of type 'void *' [-Wvoid-ptr-dereference]

4. They Are Inverses

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    *&x      // the object at the address of x  →  x
    &*p      // the address of the object p points at  →  p

Both collapse. That is not a curiosity; it is the clearest statement of what the two operators are. Measured, together with the two-level case:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
*&x == x   : true
&*p == p   : true
*pp == p   : true
**pp == x  : true
after **pp = 9, x = 9

where int** pp = &p;. The last line is §7's point arriving early: two dereferences reached x and changed it, with no rule beyond the two in §1 applied twice.

So any chain of & and * can be simplified by cancelling adjacent pairs, which is occasionally useful when reading somebody else's code and is mostly a way of checking that you have the definitions right.

5. The Collision: * in a Declaration Is Not an Operator

Here is why this chapter exists rather than being a paragraph in Chapter 8.1.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int* p = &x;      // DECLARATION: * is part of the type
    *p = 7;           // EXPRESSION:  * is an operator

Two lines, the same symbol, two unrelated jobs.

In a declaration, * says what is being declared. int* p declares p to be a pointer to int. Nothing is being dereferenced; there is nothing to dereference yet. The * is grammar, like the brackets in an array declaration.

In an expression, * is an operator that acts on a pointer's value and yields the object at that address.

The way to keep them apart is to read a declaration as type then name:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int*  p;      // type: int*     name: p
    int&  r;      // type: int&     name: r   (Chapter 8.5)
    int   n;      // type: int      name: n

Where you put the space does not matter to the compiler. int* p, int *p and int * p are the same declaration. They are not the same to a reader, and the two conventions carry an argument worth knowing about:

StyleReads asBreaks on
int* p;"p is an int*" — type-firstint* a, b; — b is an int
int *p;"*p is an int" — declarator-firstnothing, but it looks like dereferencing

The second row of the first style is a real trap, and it is the reason the int *p convention exists:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int* a, b;        // a is int*, b is int -- almost certainly not what was meant

Measured with decltype: a is int* : true, b is int : true, with zero warnings.

The * binds to the declarator, not to the type, so it applies to a alone. This curriculum writes int* p because type-first reads better and matches how the type is written everywhere else (sizeof(int*), static_cast<int*>), and it avoids the trap by declaring one variable per statement — which is Chapter 3.7's advice for an unrelated reason, arriving in a second place.

And there is a third use of &. int& r is a reference declaration, and the & there is part of a type exactly as * is in int* p — not the address-of operator. Chapter 8.5 is that chapter. Three uses of two symbols, and the rule for telling them apart is always the same: in a declaration it is a type; in an expression it is an operator.

6. The Frozen Example, Run

The index's own three-line example for this chapter is the whole lesson:

Azvya Education Pvt. Ltd.VLSI Mentor
deref.cpp — complete program
#include <iostream>

int main()
{
    int x = 5;
    int* p = &x;
    *p = 7;            // x is now 7

    std::cout << "x  = " << x << '\n';
    std::cout << "*p = " << *p << '\n';
    std::cout << "p  = " << static_cast<const void*>(p) << "   &x = " << static_cast<const void*>(&x) << '\n';
    std::cout << "same object? " << std::boolalpha << (p == &x) << '\n';
    return 0;
}

Measured output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
x  = 7
*p = 7
p  = 0x16f49e8b8   &x = 0x16f49e8b8
same object? true

Four lines, and the first one is the point.

x is 7. The statement that changed it was *p = 7;, which does not contain the name x. Nothing else in the program assigns to x.

*p is also 7 — because *p and x are the same object, reached two ways.

p and &x are the same address, and p == &x is true, which is the formal version of the same statement.

That is dereferencing doing exactly what §1 said: *p produces the object at the address, and assigning to it assigns to that object. No indirection beyond the definition is involved.

7. Reading Declarations and Expressions Fluently

A short drill, because fluency here is mostly pattern recognition.

Code* or & isMeans
int* p;part of a typep is a pointer to int
p = &x;an operatorthe address of x
*p = 7;an operatorthe object p points at
int y = *p;an operatorread the object, copy its value into y
int** pp;part of a typepp points to an int*
pp = &p;an operatorthe address of the pointer p
**pp = 9;operators, twicedereference twice to reach the int
int& r = x;part of a typer is a reference — Chapter 8.5

The int** rows are worth a second look, because they need no new rule. Chapter 8.1 §3 measured that p has its own address; &p is that address and its type is int**. A pointer to a pointer is Chapter 8.1's definition applied twice.

One form is deliberately missing from that table. When a pointer points at something with members, (*p).member works and is unpleasant, so C++ provides p->member for it. There is nothing to point at with members until Chapter 9.7's structs, and Chapter 11.3 is where -> is used in earnest.

8. What to Actually Do

Read &x as "the address of x" and *p as "the object p points at". The words are more reliable than a mental picture.

Read a declaration as type-then-name. int* p is "type int*, name p".

Declare one variable per statement. It removes int* a, b; entirely.

Remember * in a declaration is not an operator. That one distinction resolves most of the confusion.

Treat every dereference as having a precondition — that p holds the address of a live object. Chapters 8.3 and 8.8 are about when it does not.

Cancel *& and &* when reading dense code. They are inverses.

9. Mistakes Worth Making Once

Reading int* p as a dereference. In a declaration * is part of the type; there is nothing to dereference yet.

Writing int* a, b; expecting two pointers. b is an int — the * binds to the declarator.

Taking the address of a literal. &5 is rejected; there is no object.

Dereferencing a void*. It carries no promise about what is there, so there is nothing to produce.

Expecting *p = 7; to change p. It changes the object p points at; p itself is untouched.

Forgetting that *p is the object, not a copy. That is why it can be assigned to.

Confusing the & in int& r with address-of. Declaration versus expression, again — Chapter 8.5.

Assuming a dereference is self-checking. Nothing in *p = 7; says whether p is valid.

10. What You Should Be Able to Say Now

  1. & takes an object of type T and produces its address, of type T*.
  2. * takes an address of type T* and produces the object of type T at that address.
  3. They are exact inverses — *&x is x and &*p is p.
  4. & requires an object, so &5 is rejected for the same reason a non-const reference refuses a literal.
  5. *p produces the object, not a copy, which is why it can be assigned to.
  6. * needs the pointer's type to know how many bytes to read and how to interpret them — Chapter 8.1 §5's promise, collected.
  7. A void* cannot be dereferenced, because it makes no such promise.
  8. In a declaration, * is part of a type; in an expression, it is an operator. That one distinction resolves most confusion.
  9. int* p, int *p and int * p are identical to the compiler.
  10. int* a, b; makes b an int — the * binds to the declarator, which is why one declaration per statement is the fix.
  11. & has a third use — int& r is a reference type, not address-of (Chapter 8.5).
  12. Measured: *p = 7; left x at 7, *p at 7, p equal to &x, and p == &x true.
  13. The statement that changed x never mentioned x — that is the definition working, not a trick.
  14. Every dereference has an unchecked precondition: that p holds the address of a live object. Chapters 8.3 and 8.8.

11. Exercises

Exercise 1 — say it in words. For each, write what it means in a short sentence without drawing anything: (a) &count (b) *p (c) *&count (d) &*p (e) **pp.

Exercise 2 — type or operator? For each * or &, say which: (a) double* q; (b) q = &d; (c) *q = 1.5; (d) double e = *q; (e) double& r = d; (f) double** qq = &q;.

Exercise 3 — name the types. With int x; int* p = &x; int** pp = &p;, give the type of each expression: (a) x (b) &x (c) p (d) *p (e) &p (f) pp (g) *pp (h) **pp.

Exercise 4 — predict the output. Give the output of §6's program if *p = 7; is replaced by p = nullptr; and the last two lines are deleted. Say which variable changed.

Exercise 5 — the comma trap. int* a, b, c; declares three names. Give each one's type, then rewrite so all three are pointers, two ways.

Exercise 6 — why it is rejected. Explain why &5 does not compile, and name the chapter that measured the same restriction for a different construct.

Exercise 7 — the unchecked precondition. *p = 7; is a correct statement or an undefined one depending on something not visible in it. Say what that something is, and name the two chapters that deal with the two ways it fails.

12. Summary

  • &: object → address. *: address → object. One sentence each.
  • They are inverses; *&x and &*p both collapse.
  • & needs an object, so &5 is rejected.
  • *p is the object, not a copy — hence assignable.
  • * uses the type's promise to read and interpret; void* cannot be dereferenced.
  • In a declaration * is a type; in an expression it is an operator — the key distinction.
  • int* a, b; makes b an int; declare one per statement.
  • & in int& r is a third use — a reference type, Chapter 8.5.
  • Measured: *p = 7; set x to 7 without naming x; p == &x was true.
  • Every dereference has an unchecked precondition — Chapters 8.3 and 8.8.

Every dereference in this chapter assumed its pointer held the address of a live object, and §6's callout named that as an assumption rather than a fact. There are two ways it can be false, and they are not equally tractable. The hard one — a pointer holding an address that was valid and is not any more — waits until the end of the module. The next chapter takes the easy one: a pointer that is deliberately holding nothing at all. That case is testable, there is a correct way to spell the nothing, and the reason C++ added a new keyword for it in 2011 is measurable in three lines — because the two older spellings each pick the wrong function when overloads are involved, and one of them does not even compile.

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.