Compilazione, Linking e Traduzione

// obiettivi di apprendimento
Descrivere le fasi di traduzione di un programma
Distinguere compilatore, assemblatore e linker
Comprendere la differenza tra linguaggi compilati e interpretati
Spiegare il concetto di file oggetto ed eseguibile
📄
Slides
Compilatori, Assemblatori e Linker

Il ciclo di vita di un programma

Un programma scritto in un linguaggio ad alto livello prende il nome di codice sorgente. Il microprocessore può eseguire esclusivamente codice macchina — sequenze binarie di istruzioni. Il codice sorgente deve quindi essere tradotto prima di poter essere eseguito.

Esistono tre modalità di traduzione, ognuna adatta a contesti diversi:

COMPILAZIONE
Traduzione completa prima dell’esecuzione — produce un eseguibile autonomo
INTERPRETAZIONE
Traduzione durante l’esecuzione — l’interprete legge ed esegue riga per riga
MODELLO IBRIDO
Compilazione in bytecode intermedio, eseguito da una macchina virtuale

Linguaggi compilati — C / C++

Nei linguaggi compilati la traduzione avviene interamente prima dell’esecuzione. Il processo si articola in due fasi principali.

Fase 1 — Compilazione

01
ANALISI LESSICALE
Il compilatore legge il sorgente e identifica i token (parole chiave, operatori, identificatori)
02
ANALISI SINTATTICA E SEMANTICA
Verifica che la struttura del codice sia corretta e che i tipi siano coerenti
03
FILE OGGETTO
Genera un .o — codice macchina parziale, non ancora eseguibile autonomamente

Fase 2 — Linking

01
UNIONE FILE OGGETTO
Il linker unisce tutti i .o prodotti dalla compilazione
02
LIBRERIE ESTERNE
Collega le librerie di sistema e quelle di terze parti necessarie al programma
03
FILE ESEGUIBILE
Produce il file finale (.exe, a.out) — codice macchina completo e autonomo
/* main.c — codice sorgente */
int main() {
    return 0;
}

/* Processo di compilazione e linking */
gcc main.c  →  main.o  (compilazione)
            →  a.out   (linking)
// vantaggi del compilato
Traduzione completa prima dell’esecuzione → alte prestazioni. Il codice sorgente non è necessario a runtime — l’utente finale riceve solo l’eseguibile binario.

Linguaggi interpretati — JavaScript

Nei linguaggi interpretati la traduzione avviene durante l’esecuzione. L’interprete legge il codice sorgente, lo traduce progressivamente ed esegue immediatamente ogni istruzione — senza produrre un file eseguibile autonomo.

// script.js — eseguito direttamente dall'interprete
console.log("Ciao Mondo!");
alert("Benvenuto!");
// vantaggi
Nessuna fase di compilazione
Maggiore flessibilità
Portabile su qualsiasi sistema con interprete
// svantaggi
Nessun eseguibile autonomo
Prestazioni generalmente inferiori
Richiede il sorgente a runtime
// attenzione — i “puri interpretati” sono rari
Molti linguaggi definiti “interpretati” non lo sono più nel senso stretto. Python genera un bytecode intermedio (.pyc). I motori JavaScript moderni usano compilazione JIT (Just-In-Time) che compila le parti critiche a runtime. La distinzione netta compilato/interpretato è quindi una semplificazione utile didatticamente, ma la realtà è più sfumata.

Modello ibrido a bytecode — Java

Java introduce un approccio intermedio: il codice sorgente viene compilato in bytecode — un formato intermedio non legato a nessuna architettura hardware. Il bytecode viene poi eseguito dalla JVM (Java Virtual Machine), presente su ogni piattaforma.

// HelloWorld.java — sorgente
public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, World!");
  }
}

// Processo
javac HelloWorld.java  →  HelloWorld.class  (bytecode)
java HelloWorld        →  esecuzione via JVM
step 01
COMPILAZIONE
.java.class (bytecode)
step 02
CARICAMENTO JVM
La JVM carica il bytecode e gestisce il linking dinamicamente
step 03
ESECUZIONE
La JVM interpreta o JIT-compila il bytecode su qualsiasi piattaforma

Assemblatore e linguaggio Assembly

L’assemblaggio è il processo di traduzione specifico del linguaggio Assembly. A differenza dei linguaggi ad alto livello, qui la corrispondenza tra istruzione simbolica e codice macchina è quasi diretta — ogni istruzione Assembly corrisponde a una (o pochissime) istruzioni macchina.

ASSEMBLATORE
Traduce istruzioni simboliche (MOV AX, BX) in codice macchina
Produce un file oggetto (.o o .obj)
Corrispondenza quasi 1:1 con il binario
LINKER
Unisce più file oggetto in un unico eseguibile
Risolve i riferimenti tra moduli diversi
Produce il file eseguibile finale
; programma.asm — sorgente Assembly
MOV AX, BX     ; istruzione simbolica

; Processo
nasm programma.asm  →  programma.o    (assemblaggio)
ld   programma.o    →  programma.exe  (linking)

Sintesi comparativa

LinguaggioCompilazioneAssemblaggioLinkingFile prodotto
C / C++.exe / a.out
Assembly.exe / a.out
Interpretato purosolo dinamiconessuno
Java (ibrido)✔ bytecodedinamico (JVM).class

Collegamento al corso — microprocessore 8086

Nel contesto dello studio del microprocessore 8086 lavoreremo direttamente con il linguaggio Assembly. Questo significa che:

// assemblaggio
Ogni istruzione Assembly che scriviamo viene tradotta in codice macchina reale dall’assemblatore
// linking
Il linker unisce i moduli e produce l’eseguibile che viene caricato in memoria dalla CPU
// esecuzione
Il codice prodotto è codice macchina reale — eseguito direttamente dalla CPU, ciclo per ciclo
// perché studiare questo
La traduzione di un programma non è un passaggio secondario — è il momento in cui il codice diventa eseguibile dall’hardware. Comprendere compilatori, assemblatori e linker è essenziale per capire cosa accade concretamente quando un programma viene caricato in memoria ed eseguito dal microprocessore 8086.

Riepilogo

  • Il codice sorgente deve essere tradotto in codice macchina prima dell’esecuzione
  • Nei linguaggi compilati (C/C++) la traduzione avviene prima dell’esecuzione — produce un eseguibile autonomo ad alte prestazioni
  • Il compilatore genera file oggetto (.o) — codice macchina parziale non ancora collegato
  • Il linker unisce i file oggetto e le librerie producendo il file eseguibile finale
  • Nei linguaggi interpretati (JavaScript) la traduzione avviene a runtime — nessun eseguibile autonomo
  • Il modello ibrido (Java) compila in bytecode eseguito dalla JVM — portabile su qualsiasi piattaforma
  • L’assemblatore traduce Assembly in codice macchina con corrispondenza quasi 1:1
  • Nel corso 8086 usiamo Assembly — il codice prodotto è codice macchina reale eseguito direttamente dalla CPU

Lascia un commento