Menu
Coddy logo textTech

Cos'è un compilatore?

Un compilatore è un programma che traduce il codice sorgente scritto in un linguaggio di programmazione in codice macchina, o in un'altra forma di livello più basso come il bytecode, prima che il programma venga eseguito. Il programma tradotto può poi girare senza il sorgente originale.

Di Kevin Spektor, Cofondatore e CTO

Aggiornato il 24 settembre 2026

Codice sorgente
  1. 1print("Hello")
  2. 2total = 2 + 3
  3. 3print(total)
  4. 4print("Done")
Output
Compilazioni0
Esecuzioni0

Compila una volta, poi esegui il programma tutte le volte che vuoi: il contatore delle esecuzioni sale, quello delle compilazioni no. Attiva l'errore di battitura e il compilatore rifiuta l'intero programma, quindi non viene stampato nulla.

Salva questo programma C come hello.c:

#include <stdio.h>

int main(void) {
    int price = 4;
    printf("Total: %d\n", price * 3);
    return 0;
}

Poi scrivi gcc hello.c -o hello. Per un attimo non succede niente di visibile, poi accanto al sorgente compare un nuovo file chiamato hello. Quel file è l'output del compilatore: codice macchina che il tuo processore può eseguire. Cancella hello.c e ./hello funziona ancora, perché tutto ciò che serve al programma è già stato tradotto.

gcc hello.c -o hello
./hello
Total: 12

Come funziona un compilatore, passo per passo

Un compilatore legge il codice sorgente come testo semplice e lo elabora per fasi. Prendi la singola riga total = price * 3:

  1. Analisi lessicale. Il testo viene diviso in token, le "parole" del linguaggio: il nome total, l'operatore =, il nome price, l'operatore *, il numero 3. Qui spazi e commenti vengono scartati.
  2. Parsing. I token vengono organizzati in un albero sintattico che registra la struttura: un'assegnazione il cui valore è una moltiplicazione di price per 3. Il codice che viola la grammatica del linguaggio si ferma qui con un errore di sintassi.
  3. Analisi semantica. Il compilatore controlla il significato: price è dichiarata, i tipi sono compatibili, la funzione chiamata esiste.
  4. Ottimizzazione. Il programma viene riscritto per girare più veloce o usare meno memoria senza cambiare ciò che fa. x * 2 può diventare una singola istruzione di shift, e il codice che non può mai essere eseguito viene rimosso.
  5. Generazione del codice. Il compilatore scrive l'output per una destinazione: codice macchina x86-64 o ARM, oppure bytecode per una macchina virtuale.

Per C e C++ un passo finale, il linking, unisce i tuoi file compilati con il codice delle librerie (come printf della libreria standard C) in un unico eseguibile. gcc avvia il linker per te, ed è per questo che sembra un solo comando.

Python include le prime due fasi come moduli standard, quindi puoi vederle all'opera su un input reale:

NAME 'total'
OP '='
NAME 'price'
OP '*'
NUMBER '3'
Assign(
  targets=[
    Name(id='total', ctx=Store())],
  value=BinOp(
    left=Name(id='price', ctx=Load()),
    op=Mult(),
    right=Constant(value=3)))

Quell'albero è ciò su cui lavora il resto del compilatore. Non gli importano più spazi o a capo, solo la struttura: assegna a total il risultato della moltiplicazione di price per 3.

Tempo di compilazione: errori trovati prima dell'esecuzione

Siccome un compilatore legge l'intero programma prima che ne venga eseguita una qualsiasi parte, può rifiutare gli errori in anticipo. In questo esempio nessuna delle tre righe print dentro source viene eseguita, nemmeno la prima, perché il compilatore di Python rifiuta il testo prima che inizi l'esecuzione:

Rejected before running anything:
  '(' was never closed (line 3)

I compilatori dei linguaggi a tipizzazione statica scoprono più della sintassi. Questa riga di C, in un file chiamato bad.c, è grammaticalmente corretta, ma salva del testo in una variabile intera:

int count = "five";

GCC 14 e versioni successive si fermano con un errore:

bad.c:4:17: error: initialization of 'int' from 'char *' makes integer from pointer without a cast [-Wint-conversion]

Lo stesso errore in Python emergerebbe solo quando quella riga viene eseguita. Scoprirlo in fase di compilazione è uno dei motivi per cui i team grandi scelgono linguaggi compilati e a tipizzazione statica. Un compilatore però non può scoprire tutto: dividere per un numero che si rivela zero, o leggere un file che non esiste, fallisce comunque durante l'esecuzione.

Esempi di compilatori

CompilatoreLeggeProduce
GCC, ClangC, C++Codice macchina per x86-64, ARM e altre architetture
MSVC (Microsoft Visual C++)C, C++Eseguibili Windows (.exe, .dll)
javacJavaBytecode JVM (file .class)
rustcRustCodice macchina
go buildGoUn unico file eseguibile
tscTypeScriptJavaScript

Due righe mostrano che il "codice macchina" non è l'unico output possibile. javac produce bytecode che viene eseguito dalla Java Virtual Machine, parte dell'ambiente di runtime Java. tsc trasforma TypeScript in JavaScript, un altro linguaggio di alto livello; un compilatore che fa questo viene spesso chiamato transpiler.

Il codice macchina compilato è legato a una famiglia di CPU e a un sistema operativo. Un programma compilato per Windows su x86-64 non gira su un Mac con un chip Apple serie M; compili lo stesso sorgente di nuovo per ogni destinazione.

Compilatore o interprete

Un interprete esegue direttamente il codice sorgente invece di produrre un file di programma separato, ed è così che di solito si eseguono Python e Ruby. La maggior parte degli strumenti moderni usa entrambe le idee: CPython compila il tuo file in bytecode e poi lo interpreta, e la JVM e il motore V8 di JavaScript compilano in codice macchina il codice usato spesso mentre il programma è in esecuzione. La tabella di confronto completa è nella pagina compilatore e interprete.

Falsi miti comuni

  • "Se compila, funziona." Un compilatore controlla che il programma sia valido, non che sia corretto. Gli errori logici e i fallimenti durante l'esecuzione, come la divisione per zero, passano comunque.
  • "Python non è compilato." CPython compila ogni file in bytecode prima di eseguirlo. I file .pyc nelle cartelle __pycache__ sono quel bytecode, salvato per i moduli che il tuo programma importa così l'esecuzione successiva può saltare il passaggio.
  • "Un compilatore online è un compilatore." I siti che eseguono il tuo codice nel browser lo inviano a un server che esegue tutta la toolchain (compilatore, linker e il programma stesso) e restituisce l'output. Il compilatore è solo un pezzo di tutto questo.
  • "Il codice compilato gira ovunque." Gira sulla CPU e sul sistema operativo per cui è stato costruito. La portabilità viene dal ricompilare, o da una macchina virtuale come la JVM.

Cosa leggere dopo

La documentazione di C mostra un compilatore nell'uso quotidiano: come compilare ed eseguire C passa in rassegna preprocessore, compilazione, assemblaggio e linking con veri comandi gcc. Per vedere l'altro modo di eseguire il codice, leggi compilatore e interprete, poi ambiente di runtime per capire cosa circonda un programma una volta avviato. Il corso C è un buon punto di partenza per esercitarti con un linguaggio compilato.

Domande frequenti

Quali sono le parti di un compilatore?
Un compilatore di solito si descrive in tre parti. Il front end legge il codice sorgente, lo divide in token, lo analizza in un albero sintattico e controlla i tipi. Il middle end ottimizza una forma intermedia del programma, e il back end genera codice macchina o bytecode per la CPU di destinazione.
Come fa un compilatore a compilare se stesso?
Con il bootstrapping. La prima versione di un compilatore è scritta in un linguaggio esistente, o compilata da un compilatore più vecchio, e poi viene usata per compilare la versione successiva, scritta nel nuovo linguaggio stesso. GCC, per esempio, è scritto soprattutto in C e C++ e si costruisce con un compilatore C++ esistente, spesso un GCC precedente.
Qual è la differenza tra un compilatore e un assembler?
Un compilatore traduce un linguaggio di alto livello come C o Rust, in cui una riga può diventare molte istruzioni macchina. Un assembler traduce il linguaggio assembly, in cui ogni riga corrisponde quasi uno a uno a una singola istruzione macchina. Compilatori come GCC spesso producono prima assembly e lo passano a un assembler.
Cos'è un compilatore JIT?
Un compilatore just-in-time (JIT) traduce il codice in codice macchina mentre il programma è in esecuzione, invece che prima. Osserva quali parti vengono eseguite più spesso e compila quelle, così un programma che resta in esecuzione a lungo diventa più veloce strada facendo. La JVM, il motore V8 di JavaScript e PyPy usano tutti la compilazione JIT.
Devo installare un compilatore per imparare a programmare?
All'inizio no. Gli editor nel browser e i playground online eseguono per te il compilatore o l'interprete su un server. Quando passi al tuo computer, C e C++ hanno bisogno di un compilatore come GCC o Clang, Java di un JDK e Python dell'interprete Python.
Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA