Skip to content
Series
27 posts

Reading Stanford CS107 Computer Organization & Systems

A lecture-by-lecture reading of Stanford CS107: C, memory, assembly, data representation, and systems debugging from high-level code down to the machine.

Computer Organization and Systems: From C and Pointers to x86-64 and Writing Your Own malloc | Stanford CS107 Course Overview

CS107 is Stanford's computer systems course: it goes from bits, C strings and pointers to x86-64 assembly and the runtime stack, and ends with writing your own heap allocator, across seven assignments. Lecture slides, assignment specs and worked lab solutions are public; the assignment starter code lives on Stanford's AFS and is out of reach without a campus account.

From the Course Map to the Unix Command Line | Stanford CS107 Lecture 1

Winter 2026 opens by explaining why CS107 goes below programming-language abstractions: from bytes and memory through assembly and heap allocators. It then lays out the 40/10/20/30 grading structure and closes with a first tour of the Unix command line.

A First C Program, Binary, and Hexadecimal | Stanford CS107 Lecture 2

Lecture 2 puts C back into its Unix history and development environment: headers, main, printf, argc/argv, ssh, emacs, make, and executables. It then derives 8 bits = 1 byte, 256 byte patterns, and reliable conversion among decimal, binary, and hexadecimal.

Integers, Bytes, and Two's Complement | Stanford CS107 Lecture 3

Lecture 3 starts with 32/64-bit address spaces, derives the ranges of unsigned and two's-complement signed integers, inversion-plus-one, and shared addition hardware, then separates unsigned modular arithmetic from C signed overflow and tests the model against four failure cases.

Bitwise Operators, Conversions, and Masks | Stanford CS107 Lecture 4

Lecture 4 first shows that signed/unsigned conversion can preserve bits while changing meaning, that mixed comparisons may surprise, and how sign extension, zero extension, and truncation alter width. It then derives AND, OR, NOT, XOR, and bitmask idioms for testing, setting, clearing, and combining fields.

Bit Shifts, Bit Tricks, and GDB | Stanford CS107 Lecture 5

Lecture 5 extends masks to shifts, power-of-two and popcount tricks, then uses an absolute-value example to expose signed intermediate overflow at INT_MIN. Its second half establishes a GDB workflow around breakpoints, execution control, formatted printing, memory examination, and backtraces.

A C String Is Not a Type but a Memory Contract | Stanford CS107 Lecture 6

CS107 Lecture 6 reduces C strings to character arrays, a terminator, and an address: every convenience in strlen, strcmp, strcpy, strncpy, and strcat depends on the caller preserving capacity and termination invariants.

From String Search to Buffer Overflows—Input Validation Is Not Capacity Checking | Stanford CS107 Lecture 7

CS107 Lecture 7 builds pointer-based string scanning with strchr, strstr, and strspn, then shows why valid content can still overflow a buffer: safety requires input rules, destination capacity, termination, and memory-error detection.

A Pointer Is Not Magic, but a Copyable Address | Stanford CS107 Lecture 8

CS107 Lecture 8 starts with address-of and dereference, explains why C pointer parameters are still passed by value, and shows how int *, char *, and char ** can modify caller-owned ints, chars, and pointers respectively.

An Array Is Not a Pointer, but They Cooperate in Expressions | Stanford CS107 Lecture 9

CS107 Lecture 9 uses seven C-string rules to separate array objects, pointer variables, and string literals: arrays often convert to first-element pointers in expressions, but storage, assignment, mutability, and sizeof remain different.

Stack vs. Heap Is About Lifetime and Ownership, Not Just Speed | Stanford CS107 Lecture 10

CS107 Lecture 10 moves from sizeof and pointer arithmetic to stack-frame lifetime: returning a local array leaves a dangling pointer; malloc crosses function returns but makes NULL handling, size arithmetic, ownership, free, and leaks the programmer's responsibility.

How void * Gives C Generics Without Pretending Types Still Exist | Stanford CS107 Lecture 11

CS107 Lecture 11 finishes the heap contracts of calloc, strdup, free, and realloc, then turns several typed swap functions into void * plus a byte count: C generics do not preserve an unknown type; they explicitly transfer responsibility for addresses, widths, and interpretation.

Function Pointers Inject Ordering into Generic C | Stanford CS107 Lecture 12

CS107 Lecture 12 first uses char * for byte-wise generic swap and rotate, then uses a function pointer to separate bubble sort's traversal mechanism from its ordering rule: void * abstracts data types, while callbacks abstract behavior.

From Comparators to a Fully Generic Bubble Sort | Stanford CS107 Lecture 13

CS107 Lecture 13 upgrades a Boolean callback to a three-way comparator, then combines void *, element width, and const void * callbacks into a fully generic bubble sort before mapping the design to qsort, bsearch, lfind, and lsearch.

From C to x86-64, Reading Disassembly for the First Time | Stanford CS107 Lecture 14

CS107 Lecture 14 dissects the ten x86-64 instructions for sum_array: addresses and machine bytes appear on the left, AT&T assembly on the right, and the reader's job is to recover C-level effects from opcodes, operands, registers, and control flow—not to write assembly from scratch.

Reading x86-64 Addressing Modes Without Confusing Addresses and Values | Stanford CS107 Lecture 15

CS107 Lecture 15 decomposes x86-64 mov operands into immediate, register, absolute, indirect, displacement, indexed, and scaled-indexed forms, then unifies pointer dereference and array access with D + R[b] + R[i]×s.

From Subregisters to x86-64 Arithmetic and Logic | Stanford CS107 Lecture 16

CS107 Lecture 16 connects b/w/l/q data widths, subregisters, movs/movz, lea, calling conventions, arithmetic and logic, and shifts through one method: establish operand width before tracing sources, destinations, and real memory accesses.

From Multiply and Divide to x86-64 Control Flow | Stanford CS107 Lecture 17

CS107 Lecture 17 completes full-width x86-64 multiplication and division, traces %rip through instruction bytes, and uses direct and indirect jmp to show how execution leaves its default sequential path.

From Condition Codes to x86-64 Loops | Stanford CS107 Lecture 18

CS107 Lecture 18 connects ZF/SF/CF/OF to cmp, test, signed and unsigned conditional jumps, then reconstructs if statements, loops, dynamic instruction counts, setcc, and cmovcc.

Understanding x86-64 Function Calls and Calling Conventions | Stanford CS107 Lecture 19

CS107 Lecture 19 traces %rsp, push/pop, call/ret, parameters, return values, stack locals, and caller/callee register discipline to build the ABI contract that preserves data and control across functions.

After Reverse Engineering, Ask About Privacy and Trust Before Building a Heap Allocator | Stanford CS107 Lecture 20

CS107 Lecture 20 places reverse-engineering capability in an ethical context: privacy has individual and social models, while trust combines reliance with a risk of betrayal. It then reviews process memory and shifts from heap-allocation client to allocator implementer.

A First Heap Allocator and the Tension Between Speed and Space | Stanford CS107 Lecture 21

CS107 Lecture 21 starts with alignment, throughput, and utilization, then uses a bump allocator and an implicit free list to explain metadata, splitting, placement, internal and external fragmentation, and the need to coalesce freed blocks.

Why an Explicit Free List Lives in Two Orders at Once | Stanford CS107 Lecture 22

CS107 Lecture 22 replaces an implicit list with an explicit free list. Searches visit only reusable blocks, but every free block now has both physical neighbors and logical links, so unlinking, coalescing, and reinsertion must preserve both structures.

The Allocator Invariants Behind In-Place realloc | Stanford CS107 Lecture 23

CS107 Lecture 23 advances the explicit free list to in-place realloc: split a useful remainder when shrinking, absorb free right neighbors when growing, and allocate-copy-free only as a fallback, while preserving both the physical heap and logical list.

Profile with Callgrind, Then Read What GCC Optimized | Stanford CS107 Lecture 24

CS107 Lecture 24 builds a measurement workflow with matrix multiplication and Callgrind, then examines GCC constant folding, common-subexpression elimination, dead-code elimination, strength reduction, code motion, and recursion-to-loop conversion. Optimization starts with bottleneck evidence.

Caching, Memory Hierarchy, and Locality | Stanford CS107 Lecture 25

CS107 Lecture 25 builds the essential cache model from a concise deck: memory access costs are nonuniform, smaller and faster layers retain data likely to be reused, and temporal and spatial locality determine whether a program benefits.

Wrap-up, Six Systems Questions, and What Comes Next | Stanford CS107 Lecture 26

CS107 Lecture 26 closes ten weeks through six big questions: representation, text, memory, generics, execution, and allocation. It checks the learning goals through the explicit allocator and points toward CS111 and other systems courses.