Zooming In: Separate Compilation, and Linking Statically¶
Remember: All-In-One Build …¶
User |
Valuable and rock-stable code |
---|---|
#include "hello.h"
int main(void)
{
hello(); // <--- HERE
return 0;
}
|
#ifndef HELLO_H
#define HELLO_H
void hello(void);
#endif
#include "hello.h"
#include <stdio.h>
void hello(void)
{
printf("Hello World\n");
}
|
Would be built like so,
$ gcc -o hello-first hello-first.c hello.c
Solution: Separate Compilation And Linking Steps¶
Goal: only a single compilation step of
hello.c
This cannot produce an executable, obviously
Compilation only: turn
hello.c
intohello.o
$ gcc -c -o hello.o hello.c
Same for both users of
hello()
hello-first.c
⟶hello-first.o
$ gcc -c -o hello-first.o hello-first.c
hello-second.c
⟶hello-second.o
$ gcc -c -o hello-second.o hello-second.c
Linking existing object files into executables
hello-first
needshello-first.o
andhello.o
$ gcc -o hello-first hello-first.o hello.o
hello-second
needshello-second.o
andhello.o
$ gcc -o hello-second hello-second.o hello.o
Note
This is referred to as static linking. Each of the resulting executables
hello-first
andhello-second
has its own copy ofhello.o
in it!As opposed to dynamic linking where
hello.o
is wrapped into a shared library (usually named likelibhello.so
). This shared library is then loaded at program startup, just likelibc.so
as we saw in Toolchain: Basics
Complication: Modification Tracking¶
Question: what if I modify hello.c
?
Answer: re-do the following steps
Re-compile
hello.o
from it$ gcc -c -o hello.o hello.c
Re-link both using executable to update their copy of
hello.o
$ gcc -o hello-first hello-first.o hello.o
$ gcc -o hello-second hello-second.o hello.o
Note
A similar dance has to be performed if you modify one of the using
files hello-first.c
or hello-second.c
.
The following directed acyclic graph reflects exactly those
relationships (an arrow A
“⟶” B
says that “If
B
is newer than A
, then A
has to be recreated from B
):
Note
The all
node is artificial in that it does not correspond to a
file, but rather means the “see if anything needs to be done” case.
Enter Makefiles¶
Problem: how would I manually track all those dependencies in a rapidly growing project?
Answer: automate it!
The following Makefile
describes exactly the situation above,
.PHONY: all
all: hello-first hello-second
hello.o: hello.c
gcc -c -o hello.o hello.c
hello-first.o: hello-first.c
gcc -c -o hello-first.o hello-first.c
hello-second.o: hello-second.c
gcc -c -o hello-second.o hello-second.c
hello-first: hello-first.o hello.o
gcc -o hello-first hello-first.o hello.o
hello-second: hello-second.o hello.o
gcc -o hello-second hello-second.o hello.o
To run the commands in that file, standing in the directory where the
Makefile
is, simply say [1] ,
$ make
gcc -c -o hello-first.o hello-first.c
gcc -c -o hello.o hello.c
gcc -o hello-first hello-first.o hello.o
gcc -c -o hello-second.o hello-second.c
gcc -o hello-second hello-second.o hello.o
As a test, modify hello-second.c
, and see how only a subset of the
commands run,
$ make
gcc -c -o hello-second.o hello-second.c
gcc -o hello-second hello-second.o hello.o
Footnotes