The Gap Between You and the Machine

Your processor cannot read C. It executes machine code: raw numbers, each one an instruction wired into the silicon. You, on the other hand, write text files. Everything in this course hangs on understanding the journey between the two, because every error message you will ever see is one of the travellers on that road telling you where the journey broke down.

Here is a complete C program, the kind every exam paper opens with:

Press run. In well under a second, four separate tools processed that text. Knowing what each one did, and in what order, is the single most useful piece of background knowledge in C programming.

Source Code Is Just Text

The file you edit, hello.c, is called source code. It is plain text: you could write it in any editor, print it, or read it aloud. The machine gains nothing from it directly. Before anything can run, a program called the compiler must translate your text into the processor's own language.

In this course the compiler is gcc, the same one used on Linux systems everywhere. When you click run on this platform, the platform invokes gcc on your code with strict settings you will meet in a moment.

The Four-Stage Pipeline

One command looks like one step:

gcc hello.c -o hello

Internally it is an assembly line with four stations:

         preprocess         compile          assemble        link
hello.c ----------> expanded ------> hello.s ------> hello.o ------> hello
(text)              source           (assembly)      (object       (executable)
                                                      file)

Preprocessing handles every line that starts with #. Your #include <stdio.h> is not magic: the preprocessor literally pastes the contents of the stdio.h file into your program, so that the compiler later knows what printf is. It also strips comments. The output is still C, just bigger.

Compiling is the real translation. The compiler reads the expanded C, checks that it follows the language's rules, and translates it into assembly language for your processor. This is where almost every error you will ever fix is reported: a missing semicolon, a misspelled variable, a type mismatch. An error here means the pipeline stops and no program is produced.

Assembling converts the assembly text into actual machine code, stored in an object file. This step almost never fails; it is a direct, mechanical translation.

Linking stitches your object file together with the pieces of the C standard library you used. You called printf, but you never wrote printf; its compiled code lives in the library, and the linker connects your call to it. The output is the executable: a file the operating system can load into memory and hand to the processor.

If you have ever seen an error mentioning an "undefined reference", that is the linker speaking: it could not find the compiled code for a name you used. Compare that with a "syntax error", which is the compiler speaking. Reading who is complaining tells you where in the journey the problem lives.

Three Places Things Go Wrong

Since each stage can fail on its own terms, it is worth naming the kinds of error before you meet them. Exam papers ask which errors the compiler can catch, and the honest answer is: only the ones about the language, never the ones about your intent.

Compile-time errors are reported by the compiler, before any program exists. They come in two flavours. A syntax error means the text is not C at all: a missing semicolon, an unbalanced brace, a keyword spelled wrong. A semantic or type error means the text parses but asks for something the language does not allow, such as adding two whole structures together. Either way the pipeline stops and you get no executable.

Link-time errors come one stage later and from a different tool. Every "undefined reference" is one.

Run-time errors are the ones no tool caught, because no tool could. A logic error is a perfectly valid C program that computes the wrong thing: write principal * rate where you meant principal * rate * years and it will compile cleanly today and every day after. A data error is a correct program fed input it cannot handle, such as a division whose divisor turns out to be zero. You find both by running the program and checking the answers against what you expected.

Between compile time and run time sit warnings: the compiler built your program but suspects it does not mean what you wrote. That is why the compiler flags later in this lesson matter, and why reading warnings catches a fair number of would-be logic errors before you ever run anything.

Running the Executable

The executable is not C anymore. Delete your source file and the executable still runs; change your source file and the executable does not notice until you compile again. They are separate artifacts. On your own machine the cycle is:

gcc hello.c -o hello      compile: text in, executable out
./hello                   run: the OS loads it, the CPU executes it

Every time you change the source, you repeat both steps. Forgetting to recompile, then wondering why the program still shows the old behaviour, is a rite of passage.

Where C Came From

C did not arrive from nowhere, and the lineage is worth a mark or two on any paper. In 1967 Martin Richards wrote BCPL, a language meant for writing system software. In 1970 Ken Thompson pared it down into a language he called B and used it on the early Unix work at Bell Laboratories. Both were typeless: a value was just a machine word, and it was up to you to remember what the bits meant.

In 1972 Dennis Ritchie, also at Bell Labs, added data types and a great deal else to B, and called the result C. Unix was then rewritten in it, which is why an operating system and a programming language ended up growing up joined at the hip.

For six years the language had no definition beyond the compiler itself. That changed in 1978, when Brian Kernighan and Dennis Ritchie published The C Programming Language. The book served as the specification for so long that the dialect it described is still called K&R C. Success then became the problem: vendors each added their own extensions and the versions drifted apart, so ANSI convened a committee in 1983 to pin the language down. The standard it approved in December 1989 is ANSI C, adopted by ISO the following year, and known ever since as C89.

Why C Spread

"List the features of C" is a stock question, and the list is short enough to hold in your head:

  • Portable. C is defined by a standard rather than by one machine, so the same source compiles and runs on a different processor and operating system with little or no change.
  • Structured. Programs are built from functions and nested blocks rather than jumps, which is what makes them readable and testable in pieces.
  • Efficient. C's types and operations map closely onto what the hardware actually does, so compiled C is fast and small. It reaches almost as low as assembly while still reading as a high-level language, which is why operating systems and compilers get written in it.
  • Extensible. A C program is a collection of functions, and the ones you write join the library's on equal terms; nothing marks a function as built in.
  • Small language, large library. C itself is famously spare, reserving only 32 keywords in ANSI C (chapter 2 lists them). Almost all of the power arrives through the standard library, reached with #include.

The Dialect This Course Speaks

That 1989 standard is the dialect this course teaches, because it is the dialect university syllabi and exam papers are written against. Standardization did not stop there: C99 arrived in 1999, followed by C11 and C17, each adding features, and most compilers accept those features silently.

Silence is a problem when your exam does not. This platform therefore compiles every program with:

gcc -std=c89 -pedantic-errors -Wall -Wextra

-std=c89 selects the 1989 language. -pedantic-errors turns anything outside that standard into a hard compile error, so a later-C habit cannot sneak into your exam answers. -Wall -Wextra switch on the compiler's warnings, and you should read every one: a warning is the compiler telling you it compiled your program but suspects it does not do what you meant.

Two consequences you will notice immediately:

  • Comments are written /* like this */. The // style belongs to C99 and later, so here it is a compile error.
  • Every variable is declared at the top of a block, before the first statement. Mixing declarations into the middle of your code is also a later-C feature.

If you have used a classroom compiler like Turbo C, this dialect will feel familiar, with one difference: gcc is a modern compiler that produces excellent error messages, and this platform also watches every run for memory mistakes using a tool called AddressSanitizer. You get the exam's language with far better instrumentation.

Key Takeaways

  • Source code is text; the processor runs machine code; the compiler pipeline bridges the gap.
  • The pipeline is preprocess -> compile -> assemble -> link, and gcc hello.c -o hello runs all four.
  • The preprocessor pastes #include files in; the compiler translates C and reports language errors; the linker connects your calls to library code and reports undefined references.
  • The compiler catches syntax and type errors and the linker catches undefined references, but logic and data errors survive to run time, because no tool knows what you meant.
  • C came from BCPL and B; Dennis Ritchie wrote it at Bell Labs in 1972, Kernighan and Ritchie's 1978 book defined K&R C, and the ANSI committee produced C89 in December 1989.
  • C's stock virtues: portable, structured, efficient, extensible, and a small language backed by a large standard library.
  • The executable is independent of the source: change the source and you must recompile.
  • This course compiles as ANSI C (C89) with -pedantic-errors, the dialect of university exams: /* */ comments, declarations at the top of a block.