Tutorial completo del comando gcc e delle sue opzioni chiave

Ultimo aggiornamento: 26/02/2026
Autore: Isaac
  • GCC è una raccolta di compilatori che segue un flusso chiaro: pre-elaborazione, compilazione, assemblaggio e collegamento, controllabili con opzioni quali -c, -S e -E.
  • Le estensioni dei file (.c, .cpp, .o, .so, .a) e le opzioni quali -o, -I, -L e -l determinano come vengono generati gli eseguibili e come vengono integrate le intestazioni e le librerie.
  • Opzioni come -Wall, -Werror, -g, -vy, -D consentono di migliorare la qualità del codice, eseguire facilmente il debug e adattare la compilazione senza toccare il codice sorgente.
  • Grazie a -save-temps, alla sintassi @file e alla separazione tra compilazione e collegamento, GCC si integra facilmente in progetti di grandi dimensioni e flussi di lavoro di compilazione avanzati.

Introduzione al comando gcc

Se programmi in C o C++ su GNU/Linux, prima o poi ti imbatterai nel comando gcc e nel suo intero ecosistema di opzioni, fasi e file . All'inizio potrebbe sembrare un mostro con mille opzioni, ma in realtà segue regole piuttosto logiche. Una volta compreso il suo funzionamento, la compilazione smette di essere "magia nera" e diventa qualcosa che puoi controllare.

In questo articolo, esamineremo passo passo come utilizzare gcc partendo da zero, cosa fa in ogni fase della compilazione, quali tipi di file genera e quali sono le opzioni più pratiche per l'uso quotidiano: dalla compilazione di un singolo file alla gestione di progetti di grandi dimensioni, librerie e avvisi del compilatore. L'obiettivo è che, al termine della lettura, sarete in grado di comprendere facilmente il funzionamento di qualsiasi comando gcc, anche relativamente complesso.

Che cos'è GCC e quali linguaggi supporta?

GCC è l'acronimo di GNU Compiler Collection . Sebbene sia più comunemente associato al linguaggio C, in realtà è un insieme di strumenti in grado di gestire diversi linguaggi: C, C++, Fortran, Ada, Objective-C e persino altri, a seconda di come è strutturato il pacchetto della distribuzione.

In quasi tutte le distribuzioni GNU/Linux troverai gcc installato o disponibile nei repositoryperché è un componente chiave per la compilazione del sistema stesso e di molti pacchetti. In genere si richiama dalla riga di comando con il programma gcc per C e g++ per C++, sebbene tutto faccia parte della stessa famiglia di strumenti.

Per verificare quale versione di GCC stai utilizzando , esegui semplicemente il seguente comando in un terminale:

gcc --version

Quel comando non compila nulla, visualizza solo la versione del compilatore, le informazioni sulla compilazione e lo scopo per cui è stato creato (per esempio, i686-linux-gnu o x86_64-linux-gnuInoltre, se hai bisogno di maggiori dettagli su come è stato configurato, puoi visualizzarli in output dettagliati utilizzando l'opzione -v, che visualizza i dati di configurazione interna, i percorsi e gli strumenti ausiliari utilizzati.

Come ogni strumento standard di GNU/Linux, gcc ha la sua pagina man . Puoi consultarla con:

man gcc

Lì troverete l'elenco completo delle opzioni, molte di più di quelle che usereste normalmente. Il manuale è lungo e corposo, ma è un riferimento essenziale quando volete sapere esattamente a cosa serve un particolare interruttore.

Compilatore GCC nel terminale

Sintassi di base del comando gcc

Un tipico comando gcc ha un formato molto semplice: opzioni seguite dai nomi dei file . Una sintesi generica potrebbe essere simile a questa:

gcc archivos...

Di seguito, gcc distingue tra parametri che sono opzioni (interruttori che iniziano con -) Y parametri che sono fileTutto ciò che non riconosce come opzione viene considerato un file e, a seconda dell'estensione, deciderà se si tratta di codice sorgente, codice oggetto o qualcos'altro.

Le opzioni in stile UNIX sono solitamente brevi e iniziano con un semplice trattino., per esempio -Wall o -cAlcune di queste opzioni brevi possono essere raggruppate, anche se in gcc è più comune vederle separate. Le opzioni "in stile GNU" sono più lunghe e iniziano con un doppio trattino, come --versionDi solito vengono elencati uno alla volta.

Molte opzioni accettano un parametro aggiuntivo, come ad esempio un numero, una directory, un nome di file o una stringa. Ad esempio, -o nombre Viene utilizzato per specificare il file di output, -I ruta per aggiungere directory di intestazione, o -L ruta per i percorsi delle librerie. È importante rispettare gli spazi e la sintassi esatta (in -I y -L Non ci devono essere spazi tra l'opzione e il percorso.

Di solito l'ordine degli argomenti non ha molta importanza. quando parliamo di file sorgente e opzioni generali, ma nel caso di librerie collegate (-lL'ordine è importante, perché il linker li risolve da sinistra a destra. Nella maggior parte dei piccoli progetti non te ne accorgerai, ma quando inizi a lavorare con molte librerie può causare mal di testa.

Estensioni dei file e il loro significato per GCC

Una delle prime cose che gcc fa quando elabora la riga di comando è Esamina l'estensione di ogni file per decidere cosa farne.. non è lo stesso a .c un .o, O .sEcco le estensioni più comuni e come vengono interpretate:

  • .c: File di codice sorgente C. Normalmente attraversa tutte le fasi: pre-elaborazione, compilazione, assemblaggio e, se necessario, collegamento.
  • .C, .cc, .cpp, .c++, .cp, .cxxFile di codice sorgente in C++. Oggigiorno è comune usare .cpp per mantenere una convenzione chiara.
  • .m: File del codice sorgente Objective-C. Durante la compilazione, è necessario collegarlo alla libreria corrispondente (ad esempio, libobjc.a in determinati ambienti) affinché il programma funzioni correttamente.
  • .i: risultato della pre-elaborazione di un file C. È codice C "semplice", con le direttive del pre-processore già risolte (#include, macro, ecc.).
  • .ii: analogo a .i Ma per C++. È il codice C++ dopo la fase di pre-elaborazione.
  • .s: codice sorgente in assembler. Se gcc riceve questo tipo di file, normalmente lo convertirà in assembler/linker a seconda della fase richiesta.
  • .o: file di codice oggetto, ovvero il risultato della compilazione/assemblaggio ma non ancora collegato. Può essere combinato con altri .o per creare l'eseguibile finale.
  • .h: file di intestazione per il preprocessore. Di solito contiene dichiarazioni, prototipi e definizioni in linea. Non viene passato direttamente sulla riga di comando di gcc; è incluso dal codice sorgente con #include.
  • Qualsiasi altra estensione o token che non sia un'opzione valida Di solito viene trattato come un file di input generico, solitamente ritenuto un codice oggetto o un file che il linker deve elaborare.
  Come risolvere l'errore ERR collegamento chiuso nel browser Chrome?

Oltre a queste estensioni di base, nei sistemi GNU/Linux è comune trovare librerie statiche con estensione .a e librerie condivise con estensione .soLe prime sono raccolte di oggetti per il collegamento statico (non molto consigliato al giorno d'oggi, se non in casi molto specifici), mentre le seconde sono librerie dinamiche caricate in fase di esecuzione.

Processo di compilazione con gcc

Fasi del processo di compilazione con GCC

Sebbene dall'esterno possa sembrare che gcc "faccia tutto in una volta sola", internamente il processo di compilazione si articola in diverse fasi ben definite . Quando si esegue un comando tipico, questi passaggi vengono generalmente concatenati in quest'ordine:

  1. PreelaboratoLe direttive sono risolte #include, macro, condizionali di compilazione (#if, #ifdefecc.). Il risultato è un file di testo in cui tutte queste sostituzioni sono già state effettuate.
  2. CompilazioneIl codice preelaborato viene tradotto in un linguaggio intermedio e infine in codice assembly per l'architettura di destinazione.
  3. AssemblaggioL'assembler converte quel codice in un file di codice oggetto (.o), che contiene istruzioni macchina e simboli irrisolti.
  4. CollegatoIl linker prende uno o più file oggetto e librerie, risolve i riferimenti tra di essi e genera il file eseguibile (o una libreria condivisa).

Le prime tre fasi lavorano su ciascun file sorgente in modo indipendente. e di conseguenza generano un file .o per sorgente. La fase di collegamento è responsabile dell'unione di tutti quegli oggetti e librerie esterne per produrre il binario finale.

GCC consente di avviare ciascuna fase separatamente utilizzando opzioni specifiche che interrompono il processo dopo un certo punto . Questo è molto utile nei progetti di grandi dimensioni, dove in genere si compilano molti file separatamente e poi li si collega, spesso utilizzando sistemi di build come make (vedi Creazione e gestione dei Makefile ).

È importante ricordare che "compilazione" e "collegamento" sono azioni distinte , anche se si utilizza un singolo comando per eseguirle entrambe. In molti ambienti professionali, la compilazione viene eseguita in modo incrementale (compilando solo ciò che è cambiato) e il collegamento viene effettuato alla fine, il che consente di risparmiare tempo e di organizzare meglio il codice.

Da un file di codice a un semplice eseguibile

Per rendere tutto questo un po' più concreto, immagina di avere una cartella nella tua directory utente chiamare Lab2 con un file sorgente C chiamato main_es.cQuel file contiene una funzione main (dove inizia l'esecuzione del programma) e qualche altra funzione ausiliaria.

Se vuoi ottenere un eseguibile senza problemi, puoi avviarlo direttamente:

gcc main_es.c

Dopo aver eseguito questo comando, un file denominato a.out, che è il nome eseguibile predefinito per gcc quando non viene specificato alcun outputNei sistemi simili a UNIX si tratta di una tradizione di lunga data, anche se nei progetti reali si vorrà quasi sempre cambiarla con qualcosa di più descrittivo.

Per eseguire questo file dalla cartella utente, poiché non si trova in nessun percorso del PATH Dal sistema dovrai digitare:

./a.out

El prefisso ./ Indica al sistema di cercare l'eseguibile nella directory corrente.Se non lo includi, l'interprete dei comandi proverà solo a localizzarlo nei percorsi predefiniti del sistema, dove non si trova il programma appena compilato.

Non appena inizi a generare più binari, vedrai che lavorare sempre con a.out Non è affatto comodo.Quindi la prima opzione essenziale da imparare è quella che ti consente di nominare l'eseguibile come preferisci.

Scegli il nome dell'eseguibile con -o

l'opzione -o Viene utilizzato per indicare a gcc il nome del file di output che si desidera generare. È possibile utilizzarlo sia per gli eseguibili che per i file intermedi (oggetti, assembler, ecc.), a seconda della fase in cui si interrompe il processo.

Continuando con l'esempio precedente, se si desidera che il binario venga chiamato main_es al posto di a.outDovresti eseguire:

gcc -o main_es main_es.c

Dopo questo comando vedrai che nella tua cartella hai almeno un file eseguibile con il nome che hai sceltoe forse anche a.out Se lo hai generato in precedenza, puoi eliminare quello che non ti serve per evitare di riempire la directory con vecchi binari.

La sintassi generale è sempre la stessa: -o nombre_salida seguito dai file di inputSe si sta compilando solo senza collegamento (ad esempio, con -c), -o Ti consentirà di scegliere il nome dell'oggetto risultante.

Esempi di utilizzo del comando gcc

Esempi base di compilazione con gcc e g++

Da questo punto in poi, gli esempi tipici sono solitamente variazioni sulla stessa idea: compilare uno o più file sorgente e decidere quale tipo di output si desidera . Alcuni casi semplici potrebbero essere i seguenti:

Per compilare un programma C chiamato hola.c e ottenere l'eseguibile predefinito a.out:

gcc hola.c

Se preferisci che l'eseguibile venga chiamato hola, basterebbe:

gcc hola.c -o hola

Per un programma scritto in C++ nel file hola.cpp La cosa normale è usare g++che è il "frontend" GCC per C++. Un esempio basilare sarebbe:

g++ hola.cpp -o hola

Quando si desidera generare solo il codice oggetto (.o) da un file sorgente, senza ancora produrre l'eseguibile, si utilizza l'opzione -cche compila e assembla ma non collegaAd esempio, per C e anche per C++:

  Il modo per ottenere tutte le immagini da iCloud al Mac

gcc -c hola.c
g++ -c hola.cpp

In entrambi i casi, otterrai dei file hola.o che è possibile poi collegare ad altri oggetti o librerie. Questo approccio è utilizzato in progetti di medie e grandi dimensioni, in cui ogni modulo viene compilato in modo indipendente.

Se si desidera che l'eseguibile venga generato in una directory specifica, ad esempio bin all'interno della tua cartella utenteÈ possibile specificare un percorso relativo o assoluto nell'opzione -o:

gcc hola.c -o ~/bin/hola

In questo modo, dopo la compilazione, il binario verrà posizionato direttamente nella directory desiderata, pronto per essere aggiunto al PATH o essere giustiziato da lì.

Controllo di ogni fase: -c, -S, -E e -save-temps

Come abbiamo visto, gcc permette di mettere in pausa il processo in diverse fasi per ispezionare o riutilizzare i risultati intermedi . Le opzioni più comunemente utilizzate a questo scopo sono:

  • -cCompila e assembla, ma non effettua collegamenti. Il risultato è un file oggetto. .o per file sorgente. Questa è la soluzione ideale quando si lavora con più moduli che vengono poi collegati tra loro.
  • -SCompila, ma non assembla. Genera codice assembly in un file con estensione . .sÈ utile per studiare il codice macchina prodotto dal compilatore.
  • -E: esegue solo il preprocessore e visualizza l'output sullo schermo (solitamente reindirizzato a un file) .i o .ii). È perfetto per analizzare come si espandono le macro e #include.
  • -salva-temps: crea gcc salva tutti i file temporanei intermedi generati durante la compilazione: l'output del preprocessore, l'output dell'assembler e l'oggetto. In questo modo è possibile rivedere il tutto insieme.

Ad esempio, se si desidera visualizzare solo l'output del preprocessore di main.c, puoi eseguire:

gcc -E main.c > main.i

Se sei interessato all'assembler risultante, puoi usare:

gcc -S main.c > main.s

Per ottenere solo l'oggetto, senza ancora collegarlo, è sufficiente:

gcc -c main.c

E se vuoi che gcc lasci tutti i file intermedi oltre all'eseguibile, puoi fare quanto segue:

gcc -save-temps main.c

Quando in seguito elenchi la directory, vedrai qualcosa di simile a questo: a.out main.c main.i main.o main.s, con tutti i passaggi disponibili per l'ispezione o il debug.

Gestisci avvisi ed errori: -Wall, -Werror, -vy -g

Un aspetto cruciale del lavoro con gcc è imparare a sfruttare i messaggi di avviso e i report dettagliati , anziché ignorarli. Diverse opzioni possono aiutarci a trarne vantaggio.

l'opzione -Wall attiva gli avvisi più importanti dal compilatore. Non si tratta letteralmente di "tutti gli avvisi esistenti", ma piuttosto di un set abbastanza completo che consente di rilevare codice potenzialmente problematico: variabili non inizializzate, conversioni sospette, funzioni senza prototipo, ecc.

Ad esempio, supponiamo questo codice in C:

#include <stdio.h>

int main(void) {
    int i;
    printf("\n Hello world  \n", i);
    return 0;
}

Quando lo compili con:

gcc -Wall main.c -o main

gcc ti avviserà che La variabile i Viene utilizzato senza essere stato inizializzatoCiò può portare a comportamenti inaspettati. Senza -WallMolti di questi avvisi passerebbero inosservati e finiresti per dare la caccia a strani bug.

Se, oltre a visualizzare gli avvisi, si desidera trattare qualsiasi avviso come se fosse un errore, è possibile utilizzare l'opzione -Werrorche trasforma gli avvisi in errori di compilazioneCiò richiede di mantenere il codice pulito, soprattutto nei progetti in cui è richiesto un elevato livello di qualità.

Immagina questo frammento di codice in C:

#include <stdio.h>

int main(void) {
    char c;
    // Uso de c sin inicializar
    printf("\n Las cosas geek  \n", c);
    return 0;
}

Compilazione con:

gcc -Wall -Werror main.c -o main

Il compilatore interromperà il processo, poiché l'avviso relativo alla variabile non inizializzata verrà trattato come un errore . Questo può sembrare un approccio rigido, ma impedisce che i problemi si insinuino nel file binario finale.

l'opzione -v attiva una modalità dettagliata Mostra tutti i comandi interni eseguiti da gcc in ogni fase, oltre a informazioni precise su percorsi, versioni e programmi ausiliari che invoca. È particolarmente utile quando qualcosa va storto durante la fase di collegamento o quando si utilizzano librerie e si desidera vedere esattamente cosa sta succedendo.

Infine, l'opzione -g Inserire nell'eseguibile le informazioni necessarie per eseguire il debug del programma con strumenti come GDBQuando si compila con -g La relazione tra le righe del codice sorgente e gli indirizzi di memoria nel binario viene mantenuta, facilitando l'ispezione di variabili, stack di chiamate e punti di interruzione.

Includere intestazioni e librerie: -I, -L e -l

Man mano che il programma cresce, è comune iniziare a utilizzare file di intestazione personalizzati e librerie esterne , oltre a quelle standard. Per facilitare questo processo, gcc offre diverse opzioni per la regolazione dei percorsi di ricerca.

l'opzione -I Viene utilizzato per aggiungere directory in cui il preprocessore cercherà i file di intestazione .hLa sintassi è importante: non devono esserci spazi tra i due. -I e il percorso. Un esempio potrebbe essere:

gcc -I/usr/include/mis_headers main.c -o main

Se il tuo codice ha un #include "mi_header.h"Il compilatore cercherà prima nella directory del file sorgente, poi nei percorsi indicati con -I e infine nelle directory di sistema standard. Puoi ripeterlo -I quante volte vuoi per aggiungere più directory di ricerca.

D'altra parte, l'opzione -L indica directory aggiuntive in cui il linker dovrebbe cercare le librerie Quando si risolvono simboli esterni. Anche in questo caso, non viene lasciato alcuno spazio tra l'opzione e il percorso, ad esempio:

gcc main.c -L/usr/lib/mis_libs -lmi_biblioteca -o main

In questo caso, quando vedi -lmi_bibliotecaIl linker tenterà di trovare file come libmi_biblioteca.so o libmi_biblioteca.a all'interno delle directory standard e anche in /usr/lib/mis_libsDi nuovo, puoi ripetere. -L per aggiungere più posizioni.

  Il modo giusto per correggere l'errore di Microsoft Excel che non risponde

L'ordine in cui inserisci il .o e le opzioni -l è solitamente rilevante: Le librerie dovrebbero andare alla fine della riga di comandoDopo gli oggetti che ne hanno bisogno, in modo che il linker possa risolvere correttamente le dipendenze. In molti semplici esempi questo passa inosservato, ma è una buona abitudine abituarsi a questo ordine fin dall'inizio.

Definisci le macro dalla riga di comando con -D

Oltre alle macro definite nel tuo codice con #define, gcc consente di impostare le macro direttamente dalla riga di comando utilizzando l'opzione -DCiò è molto utile per compilare lo stesso codice con lievi variazioni nel comportamento senza toccare il codice sorgente.

La forma base è:

gcc -DMY_MACRO main.c -o main

Con questo, ovunque nel tuo codice dove controlli #ifdef MY_MACROQuesta condizione sarà considerata vera durante la compilazione. È anche possibile assegnare valori:

gcc -DMY_MACRO=10 main.c -o main

Questo permette, ad esempio, di attivare le tracce di debug, modificare le costanti di configurazione o compilare comodamente le versioni "di test" e "di produzione", semplicemente modificando le macro definite sulla riga di comando.

Passare un elenco di opzioni utilizzando la sintassi @file

Nei casi in cui il comando gcc diventa molto lungo, pieno di percorsi, macro, flag di ottimizzazione e così via, può essere complicato tenerne traccia. In queste situazioni, gcc consente leggere le opzioni da un file utilizzando la sintassi @archivo.

L'idea è semplice: si crea un file di testo, ad esempio opt_file, in cui scrivi Tutte le opzioni separate da spazi o interruzioni di riga, proprio come li inseriresti nella riga di comando:

-Wall -Werror -Iinclude -Llib -lmi_biblioteca -DMODO_DEBUG

Quindi, durante la compilazione, puoi fare qualcosa del tipo:

gcc @opt_file main.c -o main

GCC interpreterà il contenuto di opt_file come se avessi digitato direttamente quelle opzioni, semplificando notevolmente il comando e rendendone più facile la manutenzioneÈ un'alternativa più leggera all'utilizzo di un Makefile quando non si ha ancora voglia di utilizzare strumenti più complessi.

GCC come raccolta di strumenti: compilatore, linker e altro ancora

Sebbene spesso si parli di gcc come se fosse un unico, gigantesco programma in grado di fare tutto , in realtà agisce come una sorta di orchestratore per diversi strumenti specializzati. A seconda del tipo di file e delle opzioni fornite, richiama internamente il preprocessore, il compilatore per ciascun linguaggio, l'assembler e il linker.

Se passi un file .cAd esempio, gcc richiama il compilatore CSe gli passi un .cpp o usi g++Chiamerà il compilatore C++. Con modalità dettagliata -v È possibile vedere esattamente quali comandi vengono lanciati in ogni fase, il che aiuta a comprendere la toolchain che sta dietro.

Nei progetti più grandi, è comune separare chiaramente "compilazione" e "collegamento" come attività distinte. Spesso si vedono sistemi di compilazione che Eseguono gcc per ogni file sorgente per generare gli oggetti e poi effettuano una chiamata finale per collegarli tutti insieme.Questo modo di lavorare riduce i tempi di compilazione incrementali e si integra perfettamente con strumenti come make, ninja o altri.

Inoltre, dagli stessi oggetti e librerie è possibile creare eseguibili normali, librerie statiche (.a) o librerie condivise (.so) . Per le librerie condivise in C, si utilizzano spesso comandi come:

gcc -c -Wall -Werror -fPIC Cfile.c
gcc -shared -o libCfile.so Cfile.o

qui -fPIC genera codice indipendente dalla posizione (requisito tipico per le librerie condivise) e -shared indica che vogliamo un .so invece di un normale eseguibile. Tale libreria condivisa viene quindi collegata da altri programmi con -lCfile e il corrispondente -L per il percorso.

Quando si inizia a lavorare con più file sorgente, librerie e sistemi di compilazione, termini come "Makefile", "collegamento statico/dinamico" o "oggetti condivisi" non suonano più distanti: sono semplicemente modi diversi di indicare a gcc e agli altri compilatori come combinare i vari frammenti di codice in programmi funzionanti.

In definitiva, padroneggiare il comando gcc significa superare la paura delle sue opzioni e abituarsi a pensare a file sorgente, oggetti, intestazioni e librerie come parti della stessa macchina. Una volta compresa la logica, diventa molto più facile leggere comandi di compilazione complessi, identificare i componenti mancanti in caso di errori di collegamento o ottimizzare la compilazione in base alle esigenze del progetto senza dover sempre ricorrere a modelli esterni.

makefile
Articolo correlato:
Crea e padroneggia i Makefile: automatizza la compilazione dei tuoi progetti