The build that broke at the end
A shop's till program will not build. The error says undefined reference to printf. A colleague starts reading the C code line by line.
The code is fine. The error came from the very last step of the build, which never looks at the code at all. Knowing the steps would have saved an hour.
By the end of this lesson
Note
I can name the stages a compiler takes a program through.
Key words
| Word | What it means | Example |
|---|---|---|
| Driver | a program that runs the other build programs in order | gcc |
| Preprocessor | the stage that pastes in files and replaces macros | |
| Assembler | the program that turns assembly into machine code | as |
| Object file | machine code that is not yet a whole program | hello.o |
| Symbol | a name the linker must find, such as a function | |
| Linker | the program that joins object files and libraries | ld |
The cells students fill in are left blank in this preview.
gcc runs other programs for you
When you type gcc hello.c, gcc does very little itself. It is a driver. It runs other programs, one after another, and passes each one the last one's file.
The option -### makes gcc print those commands and run none of them. The two Runs below go into the lab's folder and show just the program each command starts.
Go into the compiler folder
cd ~/compilerShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
The programs gcc would run
gcc -### -o hello hello.c 2>&1 | grep "^ " | cut -d " " -f 2Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Worked example: three programs, four stages
| Program | Stages it does | It writes |
|---|---|---|
| cc1 | Preprocess, then compile | Assembly, a .s file |
| as | Assemble | An object file, .o |
| collect2 | Link, by running ld | The program |
The cells students fill in are left blank in this preview.
Keep the file from every stage
Normally the files between stages go in a temporary folder and are deleted. The option -save-temps keeps them beside the source.
One build then leaves four files: hello.i after preprocessing, hello.s after compiling, hello.o after assembling, and hello after linking. The command file reads the start of each file and says what kind it is.
Build once, keep everything
gcc -save-temps -o hello hello.cShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
List the folder
lsShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
What kind of file is each?
file hello.i hello.s hello.o helloShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Worked example: what file said
| File | Made by | What file calls it |
|---|---|---|
| hello.i | The preprocessor | C source, ASCII text |
| hello.s | The compiler | assembler source, ASCII text |
| hello.o | The assembler | ELF 64-bit LSB relocatable |
| hello | The linker | ELF 64-bit LSB pie executable |
The cells students fill in are left blank in this preview.
An object file has holes in it
hello.o holds the machine code for square and main. But main calls printf, and printf is not in this file. It lives in the C library.
nm lists an object file's symbols. T means the code is here. U means undefined: a hole the linker must fill.
Then run the linker ld yourself, with no C library. Read what it says.
The symbols in hello.o
nm hello.oShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Link with nothing else
ld hello.o -o brokenShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
A common mistake
Warning
Many people think every build error means a mistake in their code. An undefined reference comes from the linker, after the code compiled cleanly. It means a symbol's code was never given to the last step. Check what is linked, not the line of C.
Where printf comes from
ldd helloShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Going further
Type these yourself. Copy hello.c to twice.c and change square(7) to twice(7), with no twice function anywhere. Build it with gcc -c twice.c and say which stage spoke.
Now add the line int twice(int n); above main. Build with gcc -c twice.c, read nm twice.o, then build with gcc twice.c. Explain why the two errors come from different stages.