Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C++  Module 6 – References, pointers and memory

C++ pointers: addresses, dereferencing, nullptr

What a C++ pointer holds, how * and -> reach the object, why nullptr replaced NULL, how to read const in declarations, and what a null dereference does.

  • Beginner
  • 20 minutes
  • Examples run with Clang 21.0.0, GCC 16.2.0 and Python 3.14.8
  • By MySmartCoPilot
Free: 20 minutes

What you will learn

  • Declare, assign and dereference pointers, and reach members with ->
  • Use nullptr for "no object" and check for it before dereferencing
  • Read pointer declarations that involve const from right to left
  • Choose between a pointer and a reference for a function's parameters and results

Before you start

What you’ll learn · Before you start

What you will learn

  • Declare, assign and dereference pointers, and reach members with ->
  • Use nullptr for "no object" and check for it before dereferencing
  • Read pointer declarations that involve const from right to left
  • Choose between a pointer and a reference for a function's parameters and results

Before you start

On this page

A pointer is an object whose value is the address of another object. int* p = &seats; stores the address of seats in p. Writing *p then names seats itself, so *p = 42 changes seats, and when p points to a struct, p->marks names the member marks of the object it points to. Unlike a reference, a pointer is an object of its own: it can be changed to point somewhere else, and it can hold the null pointer value, written nullptr, which means “no object”.

That flexibility makes pointers useful and also dangerous. A pointer can say “there is no answer”, which a reference cannot, but dereferencing a null pointer, or a pointer whose object no longer exists, is undefined behaviour. This lesson covers the operators &, * and ->, nullptr and why it replaced NULL, how to read const in pointer declarations, what a null dereference looks like on both compilers and under the sanitizers, and when to choose a pointer over a reference.

Taking an address and dereferencing it

The unary & operator gives the address of an object, and * applied to a pointer gives back the object:

addresses.cpp C++ · addresses.cpp
#include <print>

int main() {
    int seats = 40;
    int* p = &seats;                 // p holds the address of seats

    std::println("seats is stored at {}", static_cast<const void*>(&seats));
    std::println("p holds            {}", static_cast<const void*>(p));
    std::println("*p is {}", *p);

    *p = 42;                         // writes to seats through p
    std::println("seats is now {}", seats);
    std::println("a pointer takes {} bytes, an int {}", sizeof p, sizeof seats);
}
An address, then the object behind it Python · addresses_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra addresses.cpp -o addresses && ./addresses
g++-16 -std=c++23 -Wall -Wextra addresses.cpp -o addresses && ./addresses
""")

Output

$ clang++ -std=c++23 -Wall -Wextra addresses.cpp -o addresses && ./addresses
seats is stored at 0x16ddf744c
p holds            0x16ddf744c
*p is 40
seats is now 42
a pointer takes 8 bytes, an int 4
$ g++-16 -std=c++23 -Wall -Wextra addresses.cpp -o addresses && ./addresses
seats is stored at 0x16d6c33e4
p holds            0x16d6c33e4
*p is 40
seats is now 42
a pointer takes 8 bytes, an int 4

This output changes from run to run: the addresses are different on every run; the pattern checks that p holds exactly the address of seats

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 addresses_session.py

&seats has the type int*, “pointer to int”, and p stores that address. The two printed addresses are equal, which is what “p points to seats” means. *p reads seats through the pointer, and *p = 42 writes to it. On this 64-bit Mac a pointer takes 8 bytes whatever it points to, while an int takes 4. The address itself is a number you rarely need: the operating system places the program’s memory differently on each run, so the value changes every time, as the pattern above allows.

std::println prints only void pointers and nullptr as addresses: the standard’s list of formatters, [format.formatter.spec], has one for void*, const void* and std::nullptr_t and none for other pointer types. That is why the example converts with static_cast<const void*>(p); std::println("{}", p) with an int* does not compile.

The pointer p holds the address of seats, so *p names seats; the pointer q holds nullptr and points to no object.int* p = &seats;int* q = nullptr;p, an int*holds 0x16b1e9e7cseats, an intstored at 0x16b1e9e7cholds 40q, an int*holds nullptrno object*p names seats*q is undefined behaviour

A pointer holds an address; nullptr points nowhere

Text description of the diagram

The diagram has two rows.

  1. The first row shows int* p = &seats;. On the left is p, a variable of type int* that holds the address 0x16b1e9e7c. On the right is seats, an int stored at that address, holding 40. An arrow from p to seats is labelled "*p names seats": dereferencing p gives the object seats itself.
  2. The second row shows int* q = nullptr;. On the left is q, an int* that holds nullptr. A dashed arrow leads to a box that says "no object", labelled "*q is undefined behaviour": a null pointer points to no object, so dereferencing it is undefined behaviour.

The address 0x16b1e9e7c is an example of what addresses.cpp prints; every run prints a different one.

Programmer Calculator (Bitwise) Paste two addresses from a run, such as 0x16b1e9e80 - 0x16b1e9e7c, to see how many bytes apart two objects are.

Members through a pointer: ->

For a pointer to a struct or class, p->member is a shorter way to write (*p).member:

arrow.cpp C++ · arrow.cpp
#include <print>
#include <string>

struct Student {
    std::string name;
    int marks;
};

int main() {
    Student asha{"Asha", 72};
    Student* p = &asha;

    (*p).marks += 5;                 // the parentheses matter: *p.marks would mean *(p.marks)
    p->marks += 3;                   // the same thing, written with ->
    std::println("{} has {} marks", p->name, asha.marks);
}
Two ways to reach a member Python · arrow_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra arrow.cpp -o arrow && ./arrow
g++-16 -std=c++23 -Wall -Wextra arrow.cpp -o arrow && ./arrow
""")

Output

$ clang++ -std=c++23 -Wall -Wextra arrow.cpp -o arrow && ./arrow
Asha has 80 marks
$ g++-16 -std=c++23 -Wall -Wextra arrow.cpp -o arrow && ./arrow
Asha has 80 marks

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 arrow_session.py

Both lines add to the same marks, so Asha ends with 80. The parentheses in (*p).marks are needed because . binds more tightly than *: *p.marks would mean *(p.marks), and a pointer has no member called marks, so the compiler would reject it. That is why everyone writes ->.

nullptr: a pointer to no object

A pointer that points to nothing holds the null pointer value. Since C++11 you write it as nullptr, a literal with its own type, std::nullptr_t, that converts to every pointer type and never implicitly to an integer. Older code uses 0 or the macro NULL, and the difference shows as soon as a function is overloaded for an integer and a pointer:

nullptr_overload.cpp C++ · nullptr_overload.cpp
#include <cstddef>
#include <print>

void show(int n)  { std::println("show(int) got {}", n); }
void show(int* p) { std::println("show(int*) got {}", p == nullptr ? "a null pointer" : "an address"); }

int main() {
    show(0);           // 0 is an int
    show(nullptr);     // nullptr converts to any pointer type, never to int
#ifdef TRY_NULL
    show(NULL);        // NULL is an integer constant on these compilers
#endif
}
0, nullptr and NULL with an overloaded function Python · nullptr_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra nullptr_overload.cpp -o nullptr_overload && ./nullptr_overload
clang++ -std=c++23 -DTRY_NULL nullptr_overload.cpp -o nullptr_overload
g++-16 -std=c++23 -DTRY_NULL nullptr_overload.cpp -o nullptr_overload
""")

Output

$ clang++ -std=c++23 -Wall -Wextra nullptr_overload.cpp -o nullptr_overload && ./nullptr_overload
show(int) got 0
show(int*) got a null pointer
$ clang++ -std=c++23 -DTRY_NULL nullptr_overload.cpp -o nullptr_overload
nullptr_overload.cpp:11:5: error: call to 'show' is ambiguous
   11 |     show(NULL);        // NULL is an integer constant on these compilers
      |     ^~~~
nullptr_overload.cpp:4:6: note: candidate function
    4 | void show(int n)  { std::println("show(int) got {}", n); }
      |      ^
nullptr_overload.cpp:5:6: note: candidate function
    5 | void show(int* p) { std::println("show(int*) got {}", p == nullptr ? "a null pointer" : "an address"); }
      |      ^
1 error generated.
[exit status 1]
$ g++-16 -std=c++23 -DTRY_NULL nullptr_overload.cpp -o nullptr_overload
nullptr_overload.cpp: In function 'int main()':
nullptr_overload.cpp:11:9: error: call of overloaded 'show(NULL)' is ambiguous
   11 |     show(NULL);        // NULL is an integer constant on these compilers
      |     ~~~~^~~~~~
  • there are 2 candidates
    • candidate 1: 'void show(int)'
      nullptr_overload.cpp:4:6:
          4 | void show(int n)  { std::println("show(int) got {}", n); }
            |      ^~~~
    • candidate 2: 'void show(int*)'
      nullptr_overload.cpp:5:6:
          5 | void show(int* p) { std::println("show(int*) got {}", p == nullptr ? "a null pointer" : "an address"); }
            |      ^~~~
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 nullptr_session.py

show(0) calls the int version, because 0 is an integer before it is anything else. show(nullptr) calls the pointer version, because nullptr cannot become an int. show(NULL) does not compile on either compiler: the standard only says that NULL is an implementation-defined null pointer constant, with 0 and 0L given as possible definitions, and here it is an integer constant that converts equally well to int and to int*. The C++ Core Guidelines put it in one rule, ES.47: use nullptr rather than 0 or NULL.

A pointer converts to bool, false for the null pointer and true otherwise, so if (p) and if (p != nullptr) mean the same. Check before you dereference whenever a pointer may be null, and initialise every pointer, to nullptr if there is nothing to point to yet: a pointer variable that is never initialised holds no valid value at all.

Reading const in a pointer declaration

const can apply to the object a pointer points to, to the pointer itself, or to both. Read the declaration from right to left, starting at the name:

const_pointers.cpp C++ · const_pointers.cpp
// Two lines in this file do not compile; read each declaration from right to left.
int main() {
    int seats = 40;
    int rows = 8;

    const int* view = &seats;        // view is a pointer to a const int
    view = &rows;                    // fine: the pointer itself may change
    *view = 9;                       // error: the int may not be changed through view

    int* const fixed = &seats;       // fixed is a const pointer to an int
    *fixed = 41;                     // fine: the int may change
    fixed = &rows;                   // error: fixed always points to seats

    const int* const both = &seats;  // both is a const pointer to a const int
    (void)both;
}
The two lines that break a promise Python · const_pointers_session.py
from session import run

run("""
clang++ -std=c++23 -c const_pointers.cpp -o const_pointers.o
g++-16 -std=c++23 -c const_pointers.cpp -o const_pointers.o
""")

Output

$ clang++ -std=c++23 -c const_pointers.cpp -o const_pointers.o
const_pointers.cpp:8:11: error: read-only variable is not assignable
    8 |     *view = 9;                       // error: the int may not be changed through view
      |     ~~~~~ ^
const_pointers.cpp:12:11: error: cannot assign to variable 'fixed' with const-qualified type 'int *const'
   12 |     fixed = &rows;                   // error: fixed always points to seats
      |     ~~~~~ ^
const_pointers.cpp:10:16: note: variable 'fixed' declared const here
   10 |     int* const fixed = &seats;       // fixed is a const pointer to an int
      |     ~~~~~~~~~~~^~~~~~~~~~~~~~
2 errors generated.
[exit status 1]
$ g++-16 -std=c++23 -c const_pointers.cpp -o const_pointers.o
const_pointers.cpp: In function 'int main()':
const_pointers.cpp:8:11: error: assignment of read-only location '* view'
    8 |     *view = 9;                       // error: the int may not be changed through view
      |     ~~~~~~^~~
const_pointers.cpp:12:11: error: assignment of read-only variable 'fixed'
   12 |     fixed = &rows;                   // error: fixed always points to seats
      |     ~~~~~~^~~~~~~
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 const_pointers_session.py

Three pointer declarations with const
Criterion const int* viewint* const fixedconst int* const both
Reads as pointer to a const intconst pointer to an intconst pointer to a const int
Change the int NoYesNo
Re-point it YesNoNo
When to choose Read-only access, the common caseA target that never changesBoth promises

Clang calls *view a read-only variable and GCC a read-only location, but both stop at line 8, and both refuse to move fixed on line 12. int const* view means the same as const int* view: what matters is whether const stands left or right of the *. A const int* parameter is the pointer version of a const int& parameter, a promise that the function only reads.

When a pointer points at nothing valid

Dereferencing a null pointer is undefined behaviour, and so is dereferencing a dangling pointer, one that still holds the address of an object that has ended. Here a search function correctly returns nullptr for an id it cannot find, and the caller forgets to check:

null_member.cpp C++ · null_member.cpp
#include <print>

struct Student {
    long id;
    int marks;
};

// Returns the student with this id, or nullptr when there is none.
Student* find_student(Student* first, Student* last, long id) {
    for (Student* s = first; s != last; ++s)
        if (s->id == id) return s;
    return nullptr;
}

int main() {
    Student batch[] = {{101, 72}, {102, 91}};
    Student* found = find_student(batch, batch + 2, 102);
    std::println("102 has {} marks", found->marks);

    Student* missing = find_student(batch, batch + 2, 999);
    std::println("999 has {} marks", missing->marks);   // missing is nullptr: undefined behaviour
}
A forgotten nullptr check, three builds Python · null_member_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra null_member.cpp -o null_member && ./null_member
clang++ -std=c++23 -g -fsanitize=address null_member.cpp -o null_member && ./null_member
g++-16 -std=c++23 -g -fsanitize=undefined null_member.cpp -o null_member && ./null_member
""")

Output

$ clang++ -std=c++23 -Wall -Wextra null_member.cpp -o null_member && ./null_member
102 has 91 marks
/bin/sh: line 1: … Segmentation fault: 11  ./null_member
[exit status 139]
$ clang++ -std=c++23 -g -fsanitize=address null_member.cpp -o null_member && ./null_member
102 has 91 marks
AddressSanitizer:DEADLYSIGNAL
==…==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000008
The signal is caused by a READ memory access.
Hint: address points to the zero page.
    #8 in main null_member.cpp:21
SUMMARY: AddressSanitizer: SEGV
[22 more lines of the report left out]
/bin/sh: line 1: … Abort trap: 6           ./null_member
[exit status 134]
$ g++-16 -std=c++23 -g -fsanitize=undefined null_member.cpp -o null_member && ./null_member
102 has 91 marks
null_member.cpp:21:47: runtime error: member access within null pointer of type 'struct Student'
/bin/sh: line 1: … Segmentation fault: 11  ./null_member
[exit status 139]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 null_member_session.py

The ordinary build printed the first line and then crashed with a segmentation fault (exit status 139). The AddressSanitizer build tells you more: SEGV on unknown address 0x000000000008, a read, near address zero, at line 21. The 8 is no accident. missing is 0, and on this 64-bit platform marks sits 8 bytes into a Student, after the 8-byte long id, so missing->marks reads address 0 + 8. A crash address that is a small number usually means a member was read through a null pointer, and the number is the member’s offset. GCC’s UndefinedBehaviorSanitizer build names the bug directly: member access within null pointer of type 'struct Student'. The fix is the check the function’s contract asks for: if (missing != nullptr) before the read.

A pointer can dangle exactly like a reference. Here seat keeps the address of booked after the block that owned booked has ended:

dangling_pointer.cpp C++ · dangling_pointer.cpp
#include <print>

int main() {
    int* seat = nullptr;
    {
        int booked = 12;
        seat = &booked;
    }                                // booked ends here; seat still holds its old address
    std::println("seat {}", *seat);
}
A pointer that outlives its object Python · dangling_pointer_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra dangling_pointer.cpp -o dangling_pointer && ./dangling_pointer
g++-16 -std=c++23 -Wall -Wextra dangling_pointer.cpp -o dangling_pointer && ./dangling_pointer
clang++ -std=c++23 -g -fsanitize=address dangling_pointer.cpp -o dangling_pointer && ./dangling_pointer
""")

Output

$ clang++ -std=c++23 -Wall -Wextra dangling_pointer.cpp -o dangling_pointer && ./dangling_pointer
seat 12
$ g++-16 -std=c++23 -Wall -Wextra dangling_pointer.cpp -o dangling_pointer && ./dangling_pointer
dangling_pointer.cpp: In function 'int main()':
dangling_pointer.cpp:9:17: warning: using dangling pointer 'seat' to 'booked' [-Wdangling-pointer=]
    9 |     std::println("seat {}", *seat);
      |     ~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~
dangling_pointer.cpp:6:13: note: 'booked' declared here
    6 |         int booked = 12;
      |             ^~~~~~
seat 12
$ clang++ -std=c++23 -g -fsanitize=address dangling_pointer.cpp -o dangling_pointer && ./dangling_pointer
==…==ERROR: AddressSanitizer: stack-use-after-scope on address 0x…
READ of size 4 at 0x… thread T0
    #8 in main dangling_pointer.cpp:9
Address 0x… is located in stack of thread T0 at offset 32 in frame
    #0 in main dangling_pointer.cpp:3
    [32, 36) 'booked' (line 6) <== Memory access at offset 32 is inside this variable
SUMMARY: AddressSanitizer: stack-use-after-scope
[47 more lines of the report left out]
/bin/sh: line 1: … Abort trap: 6           ./dangling_pointer
[exit status 134]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 dangling_pointer_session.py

Clang compiled it without a warning and the program printed seat 12, which looks right. GCC’s -Wdangling-pointer, part of -Wall, saw the problem at compile time, and the sanitizer build reports stack-use-after-scope at run time, naming booked, declared on line 6. The C++ Core Guidelines rule ES.65 says it plainly: don’t dereference an invalid pointer. The dependable defence is to keep a pointer’s lifetime inside the lifetime of its object, and to run tests under sanitizers.

Pointer or reference?

Both let a function work on the caller’s object without copying it. Here is the same swap written both ways:

swap_both.cpp C++ · swap_both.cpp
#include <print>

void swap_with_pointers(int* a, int* b) {
    int old = *a;
    *a = *b;
    *b = old;
}

void swap_with_references(int& a, int& b) {
    int old = a;
    a = b;
    b = old;
}

int main() {
    int left = 1;
    int right = 2;
    swap_with_pointers(&left, &right);       // the & at the call shows that left and right may change
    std::println("after swap_with_pointers:   left {}, right {}", left, right);
    swap_with_references(left, right);       // reads like a call by value
    std::println("after swap_with_references: left {}, right {}", left, right);
}
The same swap, twice Python · swap_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra swap_both.cpp -o swap_both && ./swap_both
""")

Output

$ clang++ -std=c++23 -Wall -Wextra swap_both.cpp -o swap_both && ./swap_both
after swap_with_pointers:   left 2, right 1
after swap_with_references: left 1, right 2

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 swap_session.py

Reference or pointer
Criterion ReferencePointer
Can be null NoYes: nullptr
Re-point it NoYes
Initialise it Always requiredOptional, but do it
Use the object By its name*p and p->member
Call site swap(left, right)swap(&left, &right)
When to choose The object must exist"No object" is a valid answer

The C++ Core Guidelines give the rule as F.60: prefer T* over T& when “no argument” is a valid option. Use a reference when the object must exist, for example swap’s two arguments, and a pointer when nothing is a meaningful answer, as for a search that may find nothing. Two more conventions keep pointer code readable. A raw pointer like these never owns its object (rule R.3): it only observes something that someone else creates and destroys, a job for containers and smart pointers that the dynamic-memory lesson takes up. And swap_with_pointers(nullptr, &right) compiles and then crashes, while swap_with_references cannot be called without two objects: a reference parameter moves the check from run time to compile time.

Your turn: find_max

Write find_max, which returns a pointer to the largest element of a vector, or nullptr when the vector is empty. When the largest value appears more than once, it returns the first one. The program checks four cases:

find_max_exercise.cpp C++ · find_max_exercise.cpp
// Write find_max: return a pointer to the largest element of marks, or nullptr when marks is empty.
// When the largest value appears more than once, return a pointer to its first occurrence.
#include <print>
#include <vector>

const int* find_max(const std::vector<int>& marks) {
    (void)marks;                     // replace these two lines with your loop
    return nullptr;
}

int passed = 0;
int checks = 0;

void check(bool ok, const char* what) {
    ++checks;
    if (ok) ++passed;
    std::println("{} {}", ok ? "pass" : "FAIL", what);
}

int main() {
    const std::vector<int> batch{72, 91, 85, 91};
    const std::vector<int> rising{3, 5, 8};
    const std::vector<int> empty;

    const int* best = find_max(batch);
    check(best != nullptr && *best == 91, "finds 91 in {72, 91, 85, 91}");
    check(best == &batch[1], "points at the first 91, inside the vector");
    check(find_max(rising) == &rising[2], "finds the last element of {3, 5, 8}");
    check(find_max(empty) == nullptr, "returns nullptr for an empty vector");
    std::println("{} of {} checks passed", passed, checks);
}
The exercise as given Python · find_max_session.py
from session import run

# The exercise as given: the starting code returns nullptr for everything.
run("""
clang++ -std=c++23 -Wall -Wextra find_max_exercise.cpp -o find_max_exercise && ./find_max_exercise
""")

Output

$ clang++ -std=c++23 -Wall -Wextra find_max_exercise.cpp -o find_max_exercise && ./find_max_exercise
FAIL finds 91 in {72, 91, 85, 91}
FAIL points at the first 91, inside the vector
FAIL finds the last element of {3, 5, 8}
pass returns nullptr for an empty vector
1 of 4 checks passed

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 find_max_session.py

As given, find_max always returns nullptr, so only the empty-vector check passes. When your version is right, the last line reads 4 of 4 checks passed, with both compilers and with -fsanitize=address,undefined.

First hint: in for (const int& m : marks), m names the element itself, not a copy, so &m is the address to remember. Keep the best one in a const int* that starts as nullptr.

Then try one thing on purpose: call find_max(std::vector<int>{3, 5, 8}) and use the result on the next line. The vector is a temporary, so the pointer dangles, for the same reason as the references in the previous lesson, and AddressSanitizer reports a heap-use-after-free.

Self-check

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 After int seats = 40; int* p = &seats; *p = 42;, what is the value of seats?

    Choose one answer.

    Show the answer to question 1

    Answer: 42

    p holds the address of seats, so *p names seats itself, and *p = 42 writes 42 into it.

  2. Question 2 of 5 With void show(int); and void show(int*);, which call is guaranteed to choose the pointer version?

    Choose one answer.

    Show the answer to question 2

    Answer: show(nullptr)

    nullptr converts to any pointer type and never implicitly to an integer. 0 is an int, so show(0) picks show(int), and NULL is an implementation-defined integer constant here, which made show(NULL) ambiguous on both compilers.

  3. Question 3 of 5 Given int a = 1, b = 2; int* const p = &a;, which statement does not compile?

    Choose one answer.

    Show the answer to question 3

    Answer: p = &b;

    Read it from right to left: p is a const pointer to an int. The int can change through it, but p itself must keep pointing at a, so p = &b is an error.

  4. Question 4 of 5 A sanitizer reports SEGV on unknown address 0x000000000010 on the line std::println("{}", s->score);. What is the most likely cause?

    Choose one answer.

    Show the answer to question 4

    Answer: s is a null pointer, and score is 16 bytes into the struct

    A small address near zero means memory was read close to address 0: a member read through a null pointer, with the member's offset as the address. A pointer to a destroyed object would show an ordinary address.

  5. Question 5 of 5 A function looks up a customer by phone number and may find nobody. What should it return?

    Choose one answer.

    Show the answer to question 5

    Answer: A pointer, nullptr when nobody is found

    "No customer" is a valid answer, which a pointer can express with nullptr (Core Guidelines F.60). A reference to a local dangles, and an uninitialised pointer holds no valid value at all.

Interview questions

Fresher · What is the difference between a pointer and a reference in C++?

A reference is an alias, bound once when it is initialised to an object that already exists; it is never re-seated and never null in a valid program. A pointer is an object of its own that holds an address: it can be left uninitialised (though it should not be), it can be changed to point elsewhere, it can hold nullptr, and you reach the object with *p or p->member. Pointers also support arithmetic inside arrays, and there are pointers to pointers, which references do not have. In function signatures, use a reference when the argument must exist and a pointer when “no object” is a valid answer. Neither one owns the object, so both can dangle when the object ends first.

Key takeaways

  • A pointer holds an address: &x takes it, *p is the object, and p->m is (*p).m.
  • nullptr means “no object”; it converts to any pointer type and never implicitly to an integer, which is why it replaced 0 and NULL, and why show(NULL) was ambiguous on both compilers.
  • Read declarations from right to left: const int* points to a const int, int* const is a pointer that cannot be re-pointed.
  • Dereferencing a null or dangling pointer is undefined behaviour; a crash at a small address such as 0x8 usually means a member read through a null pointer.
  • Prefer a reference when the object must exist, and a pointer when “no object” is a valid answer; a raw pointer observes and never owns.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.