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

Adding Another main(): What About Shared Code?

  • Lets say hello() is a popular thing

  • ⟶ more code will want to use it

So here’s another user …

Users

Valuable and rock-stable code

#include "hello.h"

int main(void)
{
    hello();   // <--- HERE
    return 0;
}
#include "hello.h"
#include <stdio.h>

int main(void)
{
    printf("Ha, here's a second world greeter!\n");
    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");
}

Building all this

$ gcc -o hello-first hello-first.c hello.c
$ gcc -o hello-second hello-second.c hello.c

This is bad!

  • Code is reused, but

  • Compiled twice

  • ⟶ Build time increases as more code uses hello()

  • ⟶ This does not scale!

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 into hello.o

Compiling only: hello.c
$ gcc -c -o hello.o hello.c
  • Same for both users of hello()

    • hello-first.chello-first.o

      $ gcc -c -o hello-first.o hello-first.c
      
    • hello-second.chello-second.o

      $ gcc -c -o hello-second.o hello-second.c
      
  • Linking existing object files into executables

    • hello-first needs hello-first.o and hello.o

      $ gcc -o hello-first hello-first.o hello.o
      
    • hello-second needs hello-second.o and hello.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 and hello-second has its own copy of hello.o in it!

    • As opposed to dynamic linking where hello.o is wrapped into a shared library (usually named like libhello.so). This shared library is then loaded at program startup, just like libc.so as we saw in Toolchain: Basics

Complication: Modification Tracking

Question: what if I modify hello.c?

Answer: re-do the following steps

  1. Re-compile hello.o from it

    $ gcc -c -o hello.o hello.c
    
  2. 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):

digraph foo {
   "hello.o" -> "hello.c";
   "hello-first.o" -> "hello-first.c";
   "hello-second.o" -> "hello-second.c";
   "hello-first" -> "hello-first.o";
   "hello-first" -> "hello.o";
   "hello-second" -> "hello-second.o";
   "hello-second" -> "hello.o";
   "all" -> "hello-first";
   "all" -> "hello-second";
}

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