Computer Science › How Computers Actually Work
16 general-purpose 64-bit registers, each with addressable 32-, 16-, and 8-bit sub-portions, moved between with `mov` and its zero-/sign-extending variants — the same register file already built from flip-flops, now under its real x86-64 names.
`add`, `sub`, `imul`, `and`, `or`, `xor`, and the shifts operate directly on registers or memory, plus `lea` — load effective address — computes an address arithmetic expression without ever touching the memory it points at.
`cmp` and `test` silently set the zero, sign, overflow, and carry flags; `jcc`, `setcc`, and `cmovcc` are the instructions that actually read them back — the exact ALU flags already covered in hardware, now driving real control flow.
A compiler lowers every `if`, `while`, and `for` into the same small vocabulary of comparisons and conditional jumps — the mechanical translation ACM/IEEE CS2013 names directly as a core learning outcome for assembly language.
Every running program sees its own private layout of memory — code, initialized and uninitialized globals, a heap growing upward, a stack growing downward — a view the operating system constructs, not the raw physical RAM the machine actually has.
A LIFO region of memory that holds every function's local variables and call bookkeeping, growing downward one frame at a time and shrinking automatically the instant a function returns — the exact hardware structure the Stack ADT was named after.
`call` pushes the return address onto the stack and jumps to the callee; `ret` pops that same address back into the instruction pointer — the entire mechanism a procedure uses to hand control back to wherever it was invoked from.
A short, fixed sequence of instructions — the prologue — carves out a frame for a function's locals on entry, and a matching epilogue tears it down before returning, leaving the stack exactly as the caller left it.
The exact, binding agreement compilers on Linux/macOS follow so independently-compiled functions can call each other correctly: which registers carry the first six arguments, which register carries the return value, which registers a callee may clobber freely, and how the stack must stay aligned at every call.
A small recursive C function compiled all the way down to real x86-64 assembly, then traced call by call — every pointer, stack frame, and calling-convention rule covered in this discipline shown working together to make recursion, taken on faith since Programming & Computational Thinking, physically happen.
A pointer is an ordinary variable whose value happens to be a memory address — `&` asks an object for its address, `*` follows an address back to the object living there, and every indirect access in C reduces to just these two operators.
Memory requested explicitly at runtime with `malloc` and released explicitly with `free`, for data whose size or lifetime can't be pinned to any single function call — the actual heap that a dynamic array's doubling reallocation calls down into.
Manual memory management fails in three distinct, well-known shapes — forgetting to free (a leak), freeing twice (a double free), and using memory after it's been freed (a dangling pointer / use-after-free) — each with its own symptom and its own fix.
A typeless pointer that can point at anything but be dereferenced as nothing until it's cast — the trick `malloc`, `qsort`, and `memcpy` all use to stay generic in a language without templates.
A pointer whose pointee is executable code rather than data — the mechanism behind callbacks and dispatch tables in a language with no first-class functions of its own.
A struct lays its fields out contiguously in declaration order, with compiler-inserted padding to keep each field aligned; a union overlays every member at the very same starting address, one at a time.
Adding 1 to a pointer moves it forward by one whole element, not one byte — arithmetic is scaled by the pointee's size — and in nearly every expression an array's name silently decays into a pointer to its first element.
Writing past the end of a stack-allocated buffer doesn't just corrupt a neighboring variable — pushed far enough, it overwrites the saved return address itself, the classic first step toward hijacking a program's control flow.
Part of the Computer Science curriculum.
16 general-purpose 64-bit registers, each with addressable 32-, 16-, and 8-bit sub-portions, moved between with `mov` and its zero-/sign-extending variants — the same register file already built from flip-flops, now under its real x86-64 names.
`add`, `sub`, `imul`, `and`, `or`, `xor`, and the shifts operate directly on registers or memory, plus `lea` — load effective address — computes an address arithmetic expression without ever touching the memory it points at.
`cmp` and `test` silently set the zero, sign, overflow, and carry flags; `jcc`, `setcc`, and `cmovcc` are the instructions that actually read them back — the exact ALU flags already covered in hardware, now driving real control flow.
A compiler lowers every `if`, `while`, and `for` into the same small vocabulary of comparisons and conditional jumps — the mechanical translation ACM/IEEE CS2013 names directly as a core learning outcome for assembly language.
Every running program sees its own private layout of memory — code, initialized and uninitialized globals, a heap growing upward, a stack growing downward — a view the operating system constructs, not the raw physical RAM the machine actually has.
A LIFO region of memory that holds every function's local variables and call bookkeeping, growing downward one frame at a time and shrinking automatically the instant a function returns — the exact hardware structure the Stack ADT was named after.
`call` pushes the return address onto the stack and jumps to the callee; `ret` pops that same address back into the instruction pointer — the entire mechanism a procedure uses to hand control back to wherever it was invoked from.
A short, fixed sequence of instructions — the prologue — carves out a frame for a function's locals on entry, and a matching epilogue tears it down before returning, leaving the stack exactly as the caller left it.
The exact, binding agreement compilers on Linux/macOS follow so independently-compiled functions can call each other correctly: which registers carry the first six arguments, which register carries the return value, which registers a callee may clobber freely, and how the stack must stay aligned at every call.
A small recursive C function compiled all the way down to real x86-64 assembly, then traced call by call — every pointer, stack frame, and calling-convention rule covered in this discipline shown working together to make recursion, taken on faith since Programming & Computational Thinking, physically happen.
A pointer is an ordinary variable whose value happens to be a memory address — `&` asks an object for its address, `*` follows an address back to the object living there, and every indirect access in C reduces to just these two operators.
Memory requested explicitly at runtime with `malloc` and released explicitly with `free`, for data whose size or lifetime can't be pinned to any single function call — the actual heap that a dynamic array's doubling reallocation calls down into.
Manual memory management fails in three distinct, well-known shapes — forgetting to free (a leak), freeing twice (a double free), and using memory after it's been freed (a dangling pointer / use-after-free) — each with its own symptom and its own fix.
A typeless pointer that can point at anything but be dereferenced as nothing until it's cast — the trick `malloc`, `qsort`, and `memcpy` all use to stay generic in a language without templates.
A pointer whose pointee is executable code rather than data — the mechanism behind callbacks and dispatch tables in a language with no first-class functions of its own.
A struct lays its fields out contiguously in declaration order, with compiler-inserted padding to keep each field aligned; a union overlays every member at the very same starting address, one at a time.
Adding 1 to a pointer moves it forward by one whole element, not one byte — arithmetic is scaled by the pointee's size — and in nearly every expression an array's name silently decays into a pointer to its first element.
Writing past the end of a stack-allocated buffer doesn't just corrupt a neighboring variable — pushed far enough, it overwrites the saved return address itself, the classic first step toward hijacking a program's control flow.