Part II · C and the Machine

04 Pointers

TutorWeek 3Lab: Check the tutor's trace in gdbMemory

What still holds

A pointer is an address, not the thing it points to.

The agent may: explain snippets and quiz you; you confirm every step in gdb.

Learning objectives

After this chapter you should be able to:

Do unaided:

  1. Draw box-and-arrow diagrams of variables, pointers, arrays and pointers to pointers.
  2. Predict &x, *p, p + 1 (in elements and in bytes), *p++, (*p)++, *++p and ++*p.
  3. Write functions with out-parameters, including swap and one with two out-parameters that reports failure.
  4. Explain array decay and why sizeof(arr) changes inside a function, and pass a length instead.
  5. Read memory in gdb with p and x/, and say which const placement protects the pointer and which protects the data.

May delegate:

  1. Explanations of pointer snippets from the tutor, which you confirm step by step in gdb before accepting them.
  2. Predict-the-output drills, generated without their answers.

Opening case

A composite case, not a real student or course.

Sam's lab task was small: add up a day's temperature readings so a later function can average them. Sam walked a pointer along the array, as the lab notes suggested. The six readings came from sensor 1001, copied by hand into an array in main. Sam wrote the loop in ten minutes, tested it once and moved on to the averaging function, the part the lab would grade.

The chapter continues from here.

The loop in this chapter

Open a step to see what it asks of you and how it plays out in the Tutor phase.

  1. Predictten predict-the-output items on paper, with confidences.

    Step 1 of 6 · You, no AI

    What you hand in: A written prediction: the output, the memory state, the operation count, and how sure you are.

    Why this step: A prediction written before the run is the only way to learn whether your model of the machine is right. A miss is the most useful result there is, because it shows exactly where the model is wrong.

    The first prediction is never delegated.

    Ask yourself: What will this print, what will memory look like when it ends, and how many operations will it take?

  2. Writeswap and a function with two out-parameters (stn_mean_sd for PA1), by hand.

    Step 2 of 6 · You, no AI

    What you hand in: One small piece written by hand, to build the mental model.

    Why this step: You can only check what you could have built. Writing a small piece yourself gives you the model you will need to judge the agent's version later.

    Ask yourself: Could I write this piece again tomorrow without notes?

  3. Specifya question to the tutor about one wrong prediction, with the run that refuted it.

    Step 3 of 6 · You

    What you hand in: The header or class interface, unit tests, a complexity budget and the ownership rules.

    Why this step: The specification is the contract the code will be held to. Tests written first, a cost budget and ownership rules turn "looks right" into something you can check.

    Tutor In the Tutor phase you specify a question instead of code: what you want explained, quizzed or drilled.

    The ownership and lifetime section of a specification is never delegated.

    Ask yourself: Who allocates, who frees, what does it cost, and what input would break it?

  4. Delegatethe tutor explains as numbered claims and writes no code you submit.

    Step 4 of 6 · The agent

    What you hand in: The implementation from your specification, or an explanation in the Tutor phase, with the prompts and transcript saved.

    Why this step: The agent does the typing; you keep the judgment. Saving the prompts and the transcript makes the delegation something you can audit afterward.

    Tutor Tutor phase: the agent explains, quizzes and makes practice problems. It writes no code you submit.

    Ask yourself: Did I hand over my specification and tests, and did I save what the agent said?

  5. Audita gdb memory dump (p, x/4dw) checks each claim; a known-answer test checks your functions.

    Step 5 of 6 · You and the tools

    What you hand in: The Audit Report: tests, sanitizer and Valgrind output, your own cost derivation against a timing plot, the defects found, and a keep-or-rewrite decision.

    Why this step: What the agent says is testimony until an instrument confirms it. Tests, sanitizers, Valgrind and a timing plot are evidence.

    Tutor In the Tutor phase the audit checks the tutor's claims as well as your own code.

    The final judgment that the code is correct is never delegated.

    Ask yourself: What did the sanitizer report, does the timing plot match my derivation, and would I keep this code or rewrite it?

  6. Defendteach the confused peer that sizeof(arr) in a function is a pointer's size, with a run as evidence.

    Step 6 of 6 · You, live

    What you hand in: A five-minute oral defense.

    Why this step: If you can explain it live, you own it. The oral checks that the understanding is yours and not the agent's.

    Ask yourself: Why this bound, what input breaks it, and where is this memory freed?

What agents get wrong here

Patterns observed in agent-written code, to test for, not guarantees. If the agent no longer makes the mistake, your job is to prove it got it right, with the same instrument.

Reading *p++ as incrementing the value

How to catch it -Werror=unused-value when the result is unused; otherwise a hand trace or gdb

Section 4.3.1 · Compiler warnings, gdb, Your own trace

Computing an array's length with sizeof on a pointer parameter

How to catch it A known-answer test needing more than one element; p n in gdb; read the signature

Section 4.3.2 · gdb, Tests

Dereferencing a result documented to be NULL on failure

How to catch it A test that forces the NULL path; UBSan load of null pointer

Section 4.3.3 · UndefinedBehaviorSanitizer, Tests

What the chapter covers

  1. 4.1 The concept, by hand
    • 4.1.1 Addresses, declaring and dereferencing
    • 4.1.2 Null, and pointers you must not follow
    • 4.1.3 Out-parameters and swap
    • 4.1.4 Pointer arithmetic counts elements
    • 4.1.5 Arrays and decay
    • 4.1.6 Walking a pointer: *p++ and its relatives
    • 4.1.7 Pointers to pointers
    • 4.1.8 const placements
    • 4.1.9 Reading memory in gdb: p and x/
  2. 4.2 Predict and check
  3. 4.3 What agents get wrong here
    • 4.3.1 Precedence of * and ++
    • 4.3.2 sizeof on a decayed array
    • 4.3.3 Dereferencing a possibly null pointer
  4. 4.4 Working with the agent
  5. 4.5 Lab: Check the tutor's trace in gdb
  6. 4.6 Defend it

Cost thread

Walking a pointer and indexing do the same work (Table 4.4).

Summary

  • A pointer holds an address: &x produces one, *p follows one, and p and *p are different objects of different sizes.
  • C passes by value, so a function changes its caller's variables only through their addresses: swap and out-parameters, with a status return and null checks.
  • Pointer arithmetic counts elements: p + 1 adds sizeof *p bytes, a[i] is *(a + i), and only pointers up to one past the end are valid.
  • Arrays decay to pointers at calls, so sizeof(arr) in a function is a pointer's size and the length must be passed.
  • *p++ moves the pointer; (*p)++ changes the element. When the result is used both compile, and only a trace, gdb or a sanitizer tells you which you wrote.
  • gdb's p and x/4dw settle claims about memory, including an agent's.

Key terms

  • address: the number of a byte in memory.
  • pointer: a variable whose value is an address, typed by what lives there.
  • dereference: to follow a pointer to the object at its address, written *p.
  • null pointer: a pointer value (NULL) that points to no object.
  • out-parameter: a pointer parameter through which a function writes a result for its caller.
  • pointer arithmetic: adding an integer to a pointer, or subtracting two pointers, measured in elements.
  • one past the end: the position after an array's last element, which a pointer may hold but never dereference.
  • array decay: the automatic conversion of an array to a pointer to its first element.
  • pointer to pointer: a pointer whose target is a pointer, such as int **pp.
  • pointer to const: a pointer such as const int *p through which its target cannot be changed.
  • const pointer: a pointer such as int *const p that cannot be made to point elsewhere.

Connections

Where this chapter and another point to each other most often.

See it on the map

Further reading

  • Kernighan and Ritchie (1988), Chapter 5: pointers and arrays from the language's designers; still the clearest account of a[i] as *(a + i).
  • ISO/IEC (2018), Sections 6.3.2.1, 6.5.6 and 6.7.6.3: decay, pointer arithmetic and array parameters, as the standard states them.
  • Serebryany et al. (2012): how AddressSanitizer guards the bytes around each object, which explains the [48, 96) in the opening case.