Welcome to the real world
Author: Geoffrey David Cowne
Unlike AI, which is unpredictable, a computer program is deterministic. In other words, programs do exactly what you instruct them to do. Interestingly, instruction is precisely the correct nomenclature.
Why C? This course does not start by teaching C syntax. It starts with the computer itself: a paper machine, binary representation, registers, an ALU, memory, assembly language, and the path from source code to machine code.
C appears because it is close enough to the machine to make those earlier ideas visible. It is not presented as the only language worth learning, but as a clear bridge between human ideas and the processor that carries them out.
Once that bridge is understood, higher-level languages become easier to learn — and much less mysterious.
Note
Learning computer jargon is as important as learning how to program.
What a computer is and how it works
A computer is an electronic device designed to receive, store, manipulate and output information. At its core, it follows a simple cycle: input ➜ processing ➜ storage ➜ output. This cycle is carried out by a combination of hardware components and software instructions that work together to perform a vast range of tasks, from simple calculations to complex simulations.
Tip
In reality, a CPU can be simplified down to an adding machine. When it adds a positive number to a negative number, the result is subtraction. Multiplication is repeated addition. Exponentiation is multiplying something over and over again.
Simplified data path & paper Turing machine
This is a simulation of a paper Turing machine.
- Central Processing Unit (CPU)
The CPU, sometimes referred to as the brain of the computer, executes instructions supplied by software. It carries out arithmetic and logical operations, controls data flow between other components, and determines the speed at which tasks are performed. Modern CPUs contain multiple cores, allowing several processes to run simultaneously. - Memory (RAM)
Random‑Access Memory (RAM) provides the CPU with quick‑access storage for data that is actively being used. Unlike permanent storage, RAM is volatile — its contents disappear when the power is switched off. The larger the RAM, the more programs can run concurrently without slowing the system. Having said this, there are types of RAM which will retain the contents when the power goes down. - Storage (Hard Disk Drive or Solid‑State Drive)
Permanent storage holds programs, files and the operating system even when the computer is powered down. Hard Disk Drives (HDDs) store data magnetically on rotating platters, while Solid‑State Drives (SSDs) use flash memory, offering faster access times and greater durability. - Motherboard
This printed circuit board interconnects all the components, providing pathways (buses) for data and power. It also houses slots for expansion cards, such as graphics processors or network adaptors. - Input and output devices
Input devices (keyboard, mouse, microphone, scanner) allow users to feed data into the system, whereas output devices (monitor, printer, speakers, headphones) present the results of the computer’s processing.
The role of software
Software consists of programs written in programming languages that translate human intentions into instructions the CPU can comprehend.
Two principal categories are:
- Operating System (OS): The OS manages hardware resources, coordinates the execution of applications, and provides a user interface. Examples include Windows, macOS, and Linux.
- Application software: These are specialised programs that perform specific tasks, such as word processing, web browsing, graphic design or gaming.
When a user launches an application, the OS loads the relevant program into RAM, where the CPU can access it quickly. The CPU then follows the program’s instruction set, retrieving data from storage as required, performing calculations, and updating the display or other output devices.
How the computer processes information
- Fetching: The CPU reads the next instruction from RAM, using the program counter to keep track of its position in the instruction sequence.
- Decoding: The instruction is interpreted to determine what operation is needed (e.g., addition, data movement, comparison).
- Executing: The CPU carries out the operation, possibly using its arithmetic‑logic unit (ALU) for calculations or interacting with other components via the bus.
- Storing Results: The outcome of the operation is written back to RAM or sent to an output device.
These steps repeat millions to billions of times per second, enabling the computer to handle complex workloads seamlessly.
Putting it all together
A typical interaction might proceed as follows: a user types a query into a web browser (input). The OS loads the browser program into RAM, and the CPU processes the request, fetching data from the internet and temporarily storing it. The browser then renders the webpage on the monitor (output). Meanwhile, any files downloaded are saved to the SSD for later retrieval.
In essence, a computer is a highly organised collection of electronic parts that, guided by software, can transform raw data into useful information. Its versatility stems from the layered architecture of hardware and software, allowing it to adapt to virtually any task required by its user.
A computer program
Programs are executed by the CPU. A program is a sequence of instructions — written in a programming language — that tells the computer precisely what to do, step by step. Each instruction is ultimately reduced to a binary opcode that the CPU decodes and acts upon. The CPU has no intuition; it follows the order of operations it is given.
From source to execution
Before a program can run, it must be translated from a human-readable language into machine code. The principal routes are:
- Compiled languages: A compiler reads the entire source file and produces a standalone binary executable. C, Go and Rust follow this model. The resulting binary runs directly on the CPU without further translation.
- Interpreted languages: An interpreter reads and executes the source line by line at run time. Python and JavaScript are familiar examples.
- Hybrid approaches: The source is compiled to an intermediate bytecode, which is then executed by a virtual machine. Java and C# work this way, combining portability with ahead-of-time or just-in-time compilation.
A simple analogy
Imagine a recipe for baking a loaf of bread. The recipe lists ingredients (data) and a series of steps (instructions): mix the flour and water, add yeast, knead, prove, bake. If you follow each step faithfully and in order, you obtain a loaf. A computer program works the same way — except the “chef” is the CPU, the “recipe” is the compiled instruction sequence, and the “ingredients” are the data held in memory.
Crucially, if you omit a step or alter the order, the outcome changes — sometimes dramatically. A program that appears to work in one scenario may fail in another because the programr did not account for a particular sequence of inputs or states.
Instructions and data
At the lowest level, the CPU distinguishes between two things in memory:
- Instructions — opcodes that tell the CPU what operation to perform.
- Data — values that the instructions operate upon.
The CPU’s program counter points to the next instruction to fetch. After decoding, the instruction may reference data held in CPU registers, in RAM, or embedded within the instruction itself. The arithmetic-logic unit (ALU) performs calculations, and the result is written back to a register or a memory address.
The instruction set
Every CPU family understands a specific vocabulary of operations, known as its instruction set architecture (ISA). Common ISAs include x86-64 (used in most desktop and server processors) and ARM (used in the majority of mobile and embedded devices). Although the opcodes differ between architectures, the fundamental categories of instruction are shared:
- Data movement: copying values between registers and memory (e.g.,
MOV,LOAD,STORE). - Arithmetic: addition, subtraction, multiplication and division (e.g.,
ADD,SUB,MUL). - Logic: bitwise AND, OR, XOR and comparisons (e.g.,
AND,OR,CMP). - Control flow: jumps, branches and calls that alter the program counter (e.g.,
JMP,JZ,CALL).
A program is built from these primitive building blocks, assembled into larger structures by the programming language and its compiler.
High-level structure
Most programs — regardless of language — are organised around a handful of recurring constructs:
- Sequence: instructions execute one after another, top to bottom.
- Selection: the program branches based on a condition (
if,switch,match). - Iteration: a block of instructions repeats until a condition is met (
for,while,loop). - Subroutines: named blocks of instructions that can be called from multiple points, returning control to the caller when finished (functions, procedures, methods).
These four constructs are sufficient to express any computable algorithm. Everything else — modules, classes, traits, concurrency primitives — is a matter of organisation and abstraction layered on top.
Abstraction and the programmer’s perspective
Writing programs directly in machine code is laborious and error-prone. Programming languages exist to bridge the gap between human reasoning and CPU execution, offering progressively higher levels of abstraction.
Layers of abstraction
| Layer | Description | Example |
|---|---|---|
| Machine code | Raw binary opcodes the CPU decodes | 1011 0000 0000 0101 |
| Assembly language | Human-readable mnemonics, one per instruction | MOV AL, 5 |
| Low-level languages | Manual memory management, close to hardware | C |
| High-level languages | Automatic memory management, rich standard libraries | Python, Go |
| Domain-specific languages | Tailored to a particular problem space | SQL, regex |
Each layer hides the complexity of the one beneath. A Go programr, for instance, need not worry about register allocation or cache lines — the compiler handles those details. This layering is what makes modern software development tractable.
Why abstraction matters
Abstraction is not merely a convenience; it is a necessity. A modern operating system contains tens of millions of lines of code. Without abstraction — without the ability to reason about what a component does without simultaneously reasoning about how every underlying transistor toggles — software at that scale would be impossible to write, let alone maintain.
The trade-off is that abstraction can obscure performance characteristics and resource usage. Embedded systems programrs, who work under tight memory and timing constraints, often operate at lower levels of abstraction to retain fine-grained control. The choice of abstraction level is therefore driven by the problem domain, and this belongs to the programr.
Determinism and the limits of prediction
A running program is deterministic: given the same inputs and the same initial state, it produces the same output every time. This is the fundamental property that distinguishes programming from training a neural network, where the model’s behaviour emerges from statistical patterns rather than explicit instructions.
That said, determinism does not guarantee predictability. A program may depend on:
- External input: user keystrokes, network packets, sensor readings.
- Timing: thread scheduling, interrupt latency, clock drift.
- Shared mutable state: race conditions when multiple threads access the same data without synchronisation.
When these factors combine, a program can exhibit behaviour that is difficult to reproduce — a class of bug known colloquially as a Heisenbug (or just a bug). The program is still deterministic in principle, but the relevant inputs are not fully observable or controlled.
Good programming practice mitigates these issues:
- Minimise mutable state: prefer pure functions where output depends only on input.
- Control side effects: be explicit about what a function modifies.
- Synchronise shared access: use
mutexes, channels oratomicsto protect concurrent data. - Test edge cases: exercise boundary conditions, empty inputs, and maximum loads.
The discipline of programming is, in large part, the discipline of managing complexity so that the deterministic nature of the machine is preserved in the face of an unpredictable world.
A first program
Learning to program requires you to get on and ride, just like learning to ride a bicycle.
To ground these concepts, consider a simple program written in C. This is the common language of embedded systems and the foundation of most modern operating systems. The program prints a greeting to the standard output.
#include <stdio.h>
/* A minimal C program */
int main(void)
{
printf("Welcome to the real world.\n");
return 0;
}
Line by line:
#include <stdio.h>— instructs the preprocessor to include the standard input/output header, which declares theprintffunction.int main(void)— defines the program’s entry point. The operating system callsmainwhen the program starts.printf("Welcome to the real world.\n");— calls the library function to write a string to standard output. The\nis an escape sequence representing a newline.return 0;— returns control to the operating system, signalling successful completion.
When compiled and executed, the CPU fetches, decodes and executes each instruction that the compiler generated from this source. The result — a line of text on the screen — is the end product of millions of individual hardware operations, all choreographed by the program.
Where to go from here
The remaining articles in this series build on the foundation laid out here:
- Define your program: before writing code, write what you want your program to do — this avoids the
TAYGstyle which results in spaghetti-code at best.TAYG= Type As You Go. - Programming in C: variables, types, control flow, functions and pointers.
- Memory and the stack: how the CPU manages function calls, local variables and the call stack.
- Building and toolchains: compilers, linkers, makefiles and build systems.
- Embedded programming: cross-compilation, bare-metal execution and interfacing with hardware peripherals.
Note
Each topic peels back another layer of abstraction, moving steadily closer to the silicon while never losing sight of the programr’s perspective. The journey from
printfto a toggling GPIO pin is shorter than it appears — and far more interesting.