Learning objectives
After this chapter you should be able to:
Do unaided:
- Run the four compile stages (
-E,-S,-c, link) on a source file and explain what each one produces. - Declare variables with C's integer and floating types and predict their sizes and ranges on the course container.
- Write
printfandscanfcalls whose format specifiers match their arguments. - Read the first compiler error, fix it by hand, and explain the fix in one sentence.
- Explain overflow, signed versus unsigned arithmetic and integer division, and find the line in a short program with undefined behavior.
May delegate:
- Asking the tutor to explain a compiler message, after you have written down your own guess about what it means.
Opening case
A composite case, not a real student or course.
Jonah had a Python script that averaged a month of barometric pressure readings from the field station: one reading per minute for 30 days, 43,200 whole numbers of pascals, each near 101,000. It printed 101348.34. His first C assignment was to port it. The script was a few lines long, and he assumed the port would mostly mean adding types and semicolons. He typed the C version in ten minutes and built it with plain gcc mean_bug.c -o mean_bug. It compiled without a single warning and printed 1927.00.
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.
Predicteach line's output, and any line with undefined behavior, before compiling.
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?
Log the prediction in your work log · Predict in every chapter · Predict in Chapter 2 →
Writethe corrected version by hand, with no casts used to silence warnings.
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?
Browse the companion code · Write in every chapter · Write in Chapter 2 →
Specifyone question for the tutor about the result you still cannot explain, with your guess.
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?
Prompt and specification templates · Specify in every chapter · Specify in Chapter 2 →
Delegatethe tutor explains under the tutor prompt; it writes no code you keep.
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?
Prompt templates · Delegate in every chapter · Delegate in Chapter 2 →
Auditcheck the explanation with a UBSan build and rebuilds at
-O0and-O2.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?
The Audit Report template · Audit in every chapter · Audit in Chapter 2 →
Defendexplain the result to a partner in one minute, naming the rule and the instrument.
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?
How the loop works · Defend in every chapter · Defend in Chapter 2 →
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.
Calls signed overflow wraparound guaranteed "because of two's complement"
How to catch it UBSan build on an edge value such as INT_MAX; compare -O0 and -O2
Section 1.3.1 · UndefinedBehaviorSanitizer, Timing and log-log plots
Leaves a stale printf or scanf specifier after changing a variable's type
How to catch it Never build without -Wall -Werror; -Wformat names the right specifier
Section 1.3.2 · Compiler warnings
Silences a warning with a cast instead of fixing the types
How to catch it Search every diff for new casts; trace the values each operand can take
Section 1.3.3
What the chapter covers
- 1.1 The concept, by hand
- 1.1.1 What Python did for you
- 1.1.2 A first C program
- 1.1.3 The compile pipeline
- 1.1.4 The book's standard build line
- 1.1.5 Integer types and sizeof
- 1.1.6 Two's complement
- 1.1.7 Overflow: wrap for unsigned, no meaning for signed
- 1.1.8 Integer division, float and double
- 1.1.9 Implicit conversions
- 1.1.10 Format specifiers: printf and scanf
- 1.1.11 Undefined behavior: one program, two answers
- 1.2 Predict and check
- 1.3 What agents get wrong here
- 1.3.1 "Signed overflow wraps around"
- 1.3.2 Format-specifier mismatches
- 1.3.3 Silencing a warning with a cast
- 1.4 Working with the agent
- 1.4.1 The tutor prompt
- 1.4.2 How to ask about an error
- 1.4.3 A preview of the working loop
- 1.5 Lab: Read the error, fix it by hand
- 1.6 Defend it
Cost thread
Operation counting starts in Chapter 3. This week, take one measurement, so that claims about C being "fast" rest on your own number: sum 10 million values of 101000 + i % 700 in Python and in C. The Python version (loop.py) is Listing 1.1's loop with 10 million in place of N and print(total) at the end.
Summary
- A C file becomes a program in four stages (preprocess, compile, assemble, link), each with an inspectable output; each stage's errors mean different things.
- A C type is a fixed number of bytes; an
inthere holds about ±2.1 billion, which a month of pressures in pascals exceeds. - Unsigned arithmetic wraps by rule; signed overflow is undefined, and an optimizer that assumes it never happens gave one program two answers at
-O0and-O2. - Integer division truncates toward zero, unlike Python's
//, and the type on the left of=does not change how the right side is computed. - Implicit conversions happen silently, and a cast that silences a warning usually keeps the bug.
- Warnings as errors, UBSan and rebuilds at two optimization levels are evidence; a fluent explanation is not.
Key terms
- bit: a single binary digit, 0 or 1.
- byte: 8 bits; the unit
sizeofcounts in. - exit status: the integer a program returns to the shell when it ends (
returnfrommain); 0 means success, and the shell keeps it in$?. - preprocessor: the first compile stage, which pastes in
#includefiles and substitutes#definenames. - assembly language: a readable text form of the processor's machine instructions; what
gcc -Swrites. - header file: a file of declarations, such as
stdio.h, that#includepastes into a source file. - translation unit: a source file plus everything it includes, after preprocessing; what the compiler compiles.
- object file: machine code for one translation unit, with references to outside names unresolved.
- linker: the program that combines object files and libraries into an executable.
- standard build line: the book's compile command, with warnings as errors and sanitizers on (Section 1.1.4).
- two's complement: the signed representation in which a pattern with the top bit set means its unsigned value minus 2ⁿ.
- overflow: a result too large or too small for its type; for unsigned types it wraps modulo 2ⁿ (wraparound).
- undefined behavior: an operation on which the C standard places no requirement, so the program has no meaning.
- implementation-defined behavior: behavior each compiler must choose and document, such as the signedness of
char. - integer division: division of two integers, discarding the fraction by truncating toward zero.
- implicit conversion: a conversion C performs with no mark in the source, such as promoting small types to
int. - cast: an explicit conversion, written as a type in parentheses.
- format specifier: a placeholder such as
%dor%lfthat tellsprintforscanfan argument's type. - optimization level: a compiler setting (
-O0to-O3) for how freely the compiler may rearrange and simplify code to make it faster. - UBSan: UndefinedBehaviorSanitizer, a build option that reports undefined operations, such as signed overflow, at run time.
Connections
Where this chapter and another point to each other most often.
- Chapter 2: The Working Loop
Record your hits and misses; Chapter 2 turns them into a calibration log. ## 1.3 What agents get wrong here A coding agent is a program that answers questions about code, explains errors and, in some setups, writes files and runs commands for you. - Chapter 3: Control Flow, Functions, Recursion, and Counting Operations
Line 3 is a function declaration: the return type, the name and the parameter types, ending in a semicolon instead of a body (Chapter 3 covers functions). - Chapter 4: Pointers
scanfneeds each variable's address, written&value("wherevaluelives"; Chapter 4). - Chapter 7: Structs, Headers, and Multi-File Programs
From Chapter 7: Treat a linker explanation as testimony: accept it provisionally, then confirm with a rebuild,nmormake -n. > Use the tutor prompt from Chapter 1.
Further reading
- Kernighan and Ritchie (1988), Chapters 1 and 2: the original tutorial on C's types, operators and
printf. - ISO/IEC (2018), Sections 3.4 and 6.3: the standard's definitions of undefined behavior and its conversion rules.
- Wang et al. (2013): how compilers remove code that depends on undefined behavior in real systems software.
- Taylor et al. (2024): how model-written explanations of C compiler and runtime errors fared in a large first-year course, and why the runtime ones need checking.