From C++ to assembly recap

You have completed the first chapter of this course. You started with a language the CPU cannot read at all and ended by generating and reading real x86-64 listings yourself. Let's review the key ideas from each lesson before you test yourself.

What Is Assembly, and Why Read It?

A program exists at three levels: the C++ source you write, the machine code the CPU actually executes, and assembly language in between, a human-readable spelling of machine code in which each instruction maps almost one-to-one onto a machine instruction. Reading assembly lets you see what your code really costs, verify the optimizer, debug the truly strange bugs, and understand the consequences of undefined behavior. Assembly is tied to a specific instruction set architecture; this course teaches x86-64, the most common and best-documented target.

The Vocabulary of Assembly

An instruction is a single command to the CPU, and instructions run top to bottom unless one jumps elsewhere. The mnemonic names the action (mov, add, ret); the operands after it are what it acts on, destination first in Intel syntax. An operand is one of three things: a register (a bare name like eax, a tiny fast slot inside the CPU), a memory location (written in square brackets, like -4[rbp]), or an immediate (a literal constant baked into the instruction). A label ends with a colon and names a location; an address is simply a numbered location in memory, and a pointer is a register holding one.

The Compilation Pipeline

Building a program is a four-stage pipeline: preprocess (-E) resolves #include and macros into a translation unit, compile (-S) translates that C++ into assembly and performs all optimization, assemble (-c) turns the assembly into machine code in an object file, and link combines object files and libraries into an executable. Assembly is the output of the compile stage: the last human-readable form of your program, captured after every optimization decision has been made.

Reading Your First Listing

An unoptimized function follows a fixed shape: the prologue (push rbp / mov rbp, rsp) sets up a stack frame, arguments are stored to stack slots and reloaded, a little real work happens, and the epilogue (pop rbp / ret) tears down and returns. C++ mangles function names into symbols like _Z6squarei; tools usually show the demangled form. Most of an -O0 listing is plumbing around one or two instructions of actual computation, like the imul eax, eax at the heart of square:

square(int):
    imul edi, edi
    mov  eax, edi
    ret

That is the same function at -O2, with all the plumbing optimized away.

The Common Instructions

About a dozen instructions cover most compiler output. mov copies a value; lea computes an address without touching memory. add, sub, and imul do arithmetic, leaving the result in the destination operand. cmp and test set the flags and keep no result; they exist to feed the jump that follows. jmp always jumps, while conditional jumps (je, jne, jl, jg, ...) act on the flags, so cmp and a jump travel as a pair. push, pop, call, and ret are the machinery of function calls, with the return value coming back in eax.

Intel vs AT&T Syntax

The same machine instructions have two spellings. Intel syntax (used in this course) writes dest, src with no prefixes and states sizes with keywords like DWORD PTR only when needed. AT&T syntax (the GNU default) reverses the operand order to src, dest, prefixes registers with % and immediates with $, encodes size as a mnemonic suffix (b/w/l/q), and writes memory operands as disp(base, index, scale) instead of brackets. Add -masm=intel to make GNU tools emit Intel syntax.

Generating Assembly Yourself

You have four ways to make listings. g++ -S stops the pipeline after the compile stage and writes a .s file; add -masm=intel and -fno-asynchronous-unwind-tables to clean it up, and always state the optimization level (-O0 for literal, source-shaped code; -O2 for what ships). objdump -d -M intel disassembles a finished binary when you have no source or need the final linked result. Compiler Explorer (godbolt.org) gives live, color-coded source-to-assembly mapping, and this site's editor has a live assembly panel so you can read the assembly of every exercise you write.

Key Terminology

  • Machine code: The numeric encoding of instructions that the CPU executes directly
  • Assembly language: A human-readable spelling of machine code, roughly one instruction per machine instruction
  • Instruction set architecture (ISA): The specific processor family an assembly listing targets, such as x86-64
  • Mnemonic: The short name of an instruction's action, such as mov or add
  • Operand: What an instruction acts on; destination first in Intel syntax
  • Register: A small, fast, named storage slot inside the CPU
  • Memory operand: An operand in square brackets naming a location in RAM
  • Immediate: A literal constant written directly inside an instruction
  • Label: A name ending in : that marks a location, such as a function start or jump target
  • Translation unit: The single block of pure C++ produced by the preprocessor for one source file
  • Object file: Machine code produced by the assembler, not yet linked into an executable
  • Name mangling: The encoding of a C++ function's signature into a symbol like _Z6squarei
  • Prologue / epilogue: The stack-frame setup and teardown at the start and end of a function
  • Flags: CPU status bits set by cmp and test and read by conditional jumps
  • Intel syntax / AT&T syntax: The two notations for x86-64; Intel is dest, src, AT&T is src, dest with % and $ prefixes
  • Disassembly: Turning machine code in a binary back into assembly, e.g. with objdump -d
  • Optimization level: The -O0/-O2 setting that changes the emitted assembly more than any other flag

Looking Forward

You can now recognise the shape of a listing, but so far the registers in it have mostly been names flying past. Chapter 2 slows down and studies the machinery itself: the sixteen general-purpose registers and their conventional roles, why the same register appears as rax, eax, ax, or al, and exactly how mov, the arithmetic instructions, addressing modes, and lea work. That foundation is what turns pattern-spotting into genuine reading.