Systems Engineering

Follow one native result at a time from source and bytes through ownership, kernel I/O, concurrency, measurement, persistence, failure, and recovery.

Course details and reading size
Tutorial
Reading comfortAdjust lesson text without changing code or interface size.

Machine representation, compilation, and linking

Objective Trace one two-file C program through source, compilation, object files, linking, loading, running output, and exit status.

Core explanation

Source code is human-readable text governed by the C language; a compiler translates each source file into an object file containing machine code, data, symbols, and unresolved references for one target. A linker combines object files and required libraries, matches each referenced symbol to one definition, and writes an executable file. When the executable starts, the operating-system loader maps its code and data, prepares the process, and transfers control to the C runtime and then main. The running process writes output and returns an exit status to its parent. These are separate stages: a source error can stop compilation, a missing function definition can stop linking, an unavailable runtime dependency can stop loading, and an assertion can fail only after execution begins. The first laboratory uses two tiny C files and explicit markers so every learner can predict the same four lines without symbol inspection, assembly literacy, C++, Rust, elevated privilege, or a network. A translation unit passes through preprocessing, parsing, optimization, code generation, assembly, and linking before the loader maps an executable and its libraries into a process. Object files contain sections, symbols, relocations, debug metadata, and machine code rather than one complete runnable program. Static and dynamic linking make different size, update, startup, and deployment tradeoffs. The ABI defines calling convention, register use, stack alignment, object layout, symbol naming, and binary interfaces independently from source syntax. Inspect commands, object files, dependencies, and exit status. Keep compiler, flags, architecture, libraries, and loader paths consistent, preserve symbols, and separate compile, link, load, and runtime failures during diagnosis.

Compilation, linking, loading, and execution are separate stages with different failures and evidence.

Name the six forms before tracing the toolchain

Source is C text. A translation unit is the source after preprocessing for one compiler invocation. An object file is target-specific machine code and data that can still contain unresolved symbol references. An executable is a linked file the loader understands. A process is one running instance of that executable. Output and exit status are observations produced only after the process starts. Keeping these six forms separate prevents “the program built” from hiding where evidence actually stops.

Every stored value is represented as bits, but bits receive meaning from a C type and target contract. Integer width, signed range, floating approximation, character encoding, alignment, padding, and byte order matter when values cross a file, network, ABI, or foreign-language boundary. Chapter 1 does not ask the learner to memorize an instruction set. It asks for explicit widths and checked conversions whenever native representation would otherwise leak into a durable or shared format.

Compile two translation units before making a link claim

message.c defines message, while main.c declares and calls it. Each cc -c command parses and translates only one source file into one object file. Compilation can therefore succeed for main.c even though the referenced function body is elsewhere. The marker compile=2-objects means two expected object files were produced under the declared C17, warning, and error policy; it does not mean an executable exists.

A missing semicolon or type violation fails compilation. A missing definition does not necessarily do so because unresolved external references are allowed in an object file. Record the exact compiler, target, language mode, warnings, preprocessing inputs, optimization, and command. Later chapters inspect symbols and machine details; the beginner opening needs only enough evidence to distinguish source from object.

Link references, then let the loader create a process

The linker reads main.o and message.o, connects the reference named message to its definition, adds required runtime pieces, and writes hello. The marker link=hello belongs after that successful command. Omitting message.o creates a link failure even though both source files may be individually valid. Duplicate definitions, incompatible objects, and library-order rules on some toolchains are other link-layer problems.

Running ./hello asks the operating-system loader to map the executable and required libraries, prepare process memory, arguments, environment, and standard streams, and enter the C runtime before main. The process prints run=hello and returns zero; only then does the shell print exit=0. An unavailable dynamic dependency can stop loading before main, while a failed assertion is a runtime result after loading.

Laboratory: move one fault through compile, link, load, and run

Use a dedicated disposable directory and the C17 compiler already established in C Language. Predict the exact four baseline lines, run the script, and record the compiler identity plus exit status. Then restore the baseline before each fault: remove a semicolon, omit message.o from the link, configure one deliberately unavailable dynamic dependency inside the owned lab, and insert a failing assertion after process start.

For every fault, record which files exist, whether a process started, which diagnostic stream changed, and the final status. Do not add a debugger, assembly view, sanitizer, C++, or Rust merely to classify these four stages. Acceptance means another learner can reproduce the baseline and name the earliest failed stage from the evidence without guessing from the loudest message.

CURRICULUM CONTEXTRelated courses and the course concept model