Il processo di compilazione

Un compilatore è un programma che traduce il codice sorgente scritto in un linguaggio di programmazione ad alto livello, come C++ o Java, in un linguaggio macchina comprensibile ed eseguibile dalla CPU. Il risultato finale è un file binario eseguibile, che può essere eseguito direttamente dalla CPU senza la necessità del codice sorgente originale.

Prendiamo per esempio un semplice programma scritto in C.

#include <stdio.h>

int main (){
    puts("Hello World!");
    return 0;
}

Possiamo compilarlo con un compilatore, e possiamo ispezionare il codice assembly generato tramite degli strumenti come objdump.

$ objdump -M intel -d main

[...]

0000000000001139 <main>:
    1139:	55                   	push   rbp
    113a:	48 89 e5             	mov    rbp,rsp
    113d:	48 8d 05 c0 0e 00 00 	lea    rax,[rip+0xec0]        # 2004 <_IO_stdin_used+0x4>
    1144:	48 89 c7             	mov    rdi,rax
    1147:	e8 e4 fe ff ff       	call   1030 <puts@plt>
    114c:	b8 00 00 00 00       	mov    eax,0x0
    1151:	5d                   	pop    rbp
    1152:	c3                   	ret

Possiamo chiaramente identificare l'istruzione che chiama la funzione puts, ovvero

call   1030 <puts@plt>

e le istruzioni che fanno ritornare 0 dalla funzione (il valore di ritorno di una funzione in x86 è messo nel registro rax)

mov    eax,0x0
pop    rbp
ret

Il processo di traduzione che porta dal codice sorgente al programma eseguibile attraversa varie fasi di compilazione e ottimizzazione. Ad esempio un compilatore per il linguaggio C deve prima eseguire il preprocessore, che risolve tutte le direttive ottenendo il codice sorgente completo. Questo poi passa al compilatore vero e proprio, che trasforma un file sorgente (più nello specifico una translation unit) in codice assembly. Infine l'assemblatore trasforma il file assembly in un file oggetto.

Questo però non è ancora eseguibile, perché potrebbe avere delle dipendenze con librerie o con altri file oggetto. Queste dipendenze sono risolte dal linker, che combina tutti i file oggetto in un unico file eseguibile. Il processo di linking può essere statico, ovvero le librerie sono incorporate all'interno del binario finale, oppure dinamico, ovvero vengono lasciate delle reference alle librerie necessarie, che saranno poi caricate a tempo di esecuzione.

Processo di traduzione completo