Per Linux e' disponibile un compilatore integrato C/C++: si tratta dei comandi GNU gcc e g++, rispettivamente.
In realta g++ e' uno script che chiama gcc con opzioni specifiche per riconoscere il C++.
Il comando gcc, GNU Compiler Collection, fa parte del progetto GNU (web server www.gnu.org). Il progetto GNU fu lanciato nel 1984 da Richard Stallman con lo scopo di sviluppare un sistema operativo di tipo Unix che fosse completamente "free" software.
Cosa è GNU/Linux?
Gnu Non è Unix!
"GNU, che sta per "Gnu's Not Unix" (Gnu Non è Unix), è il nome del sistema
software completo e Unix-compatibile che sto scrivendo per distribuirlo
liberamente a chiunque lo possa utilizzare. Molti altri volontari mi
stanno aiutando. Abbiamo gran necessità di contributi in tempo, denaro,
programmi e macchine."[Richard Stallman, Dal manifesto GNU, http://www.gnu.org/gnu/manifesto.html
Si puo' determinare la versione del compilatore invocando:
gcc -v gcc version 2.96 20000731 (Red Hat Linux 7.1 2.96-98) |
I passi della compilazione
Sia gcc che g++ processano file di input attraverso uno o piu' dei seguenti passi:
1) preprocessing
-rimozione dei commenti
-interpretazioni di speciali direttive per il preprocessore denotate da "#" come:
#include - include il contenuto di un determinato file, Es. #include<math.h>
#define -definisce un nome simbolico o una variabile, Es. #define MAX_ARRAY_SIZE 1002) compilation
-traduzione del codice sorgente ricevuto dal preprocessore in codice assembly3) assembly
-creazione del codice oggetto4) linking
-combinazione delle funzioni definite in altri file sorgenti o definite in librerie con la funzione main() per creare il file eseguibile.
Alcuni suffissi di moduli implicati nel processo di compilazione:
.c modulo sorgente C; da preprocessare, compilare e assemblare
.cc modulo sorgente C++; da preprocessare, compilare e assemblare
.cpp modulo sorgente C++; da preprocessare, compilare e assemblare
.h modulo per il preprocessore; di solito non nominato nella riga di commando
.o modulo oggetto; da passare linker
.a sono librerie statiche
.so sono librerie dinamiche
gcc accetta in input ed effettua la compilazione di codice C o C++ in un solo colpo.
Consideriamo il seguente codice sorgente C:
/* il codice C pippo.c */ #include <stdio.h> int main() { puts("ciao pippo!"); return 0; }Per effettuare la compilazione
gcc pippo.cIn questo caso l' output di default e' direttamente l'eseguibile a.out.
Di solito si specifica il nome del file di output utilizzando l' opzione -o :
gcc -o prova pippo.cL'eseguibile puo' essere lanciato usando semplicemente
./provaciao pippo!Nota. Usare "./" puo' sembrare superfluo. In realta' si dimostra molto utile per evitare di lanciare involontariamente un programma omonimo, per esempio il comando "test"!
Consideriamo ora un codice sorgente C++ analogo:
// Il codice C++ pippo.cpp #include<iostream> int main() { cout<<"ciao pippo!"<<'\n'; return 0; }Questa volta compiliamo usando
g++ -o prova pippo.cpp
Per verificare il valore restituito dal programma al sistema tramite l'istruzione di return usiamo
./prova ciao pippo! echo $? 0
Passaggi intermedi di compilazione
Per compilare senza effettuare il link usare
g++ -c pippo.cppIn questo caso viene creato il file oggetto pippo.o .
Per effettuare il link usiamo
g++ -o prova pippo.o
I messaggi del compilatore
Il compilatore invia spesso dei messaggi all'utente. Questi messaggi si possono classificare in due famiglie: messaggi di avvertimento (warning messagges) e messaggi di errore (error messagges). I messaggi di avvertimento indicano la presenza di parti di codice presumibilmente mal scritte o di problemi che potrebbero avvenire in seguito, durante l'esecuzione del programma. I messaggi di avvertimento non interrompono comunque la compilazione.I messaggi di errore invece indicano qualcosa che deve essere necessariamente corretto e causano l'interruzione della compilazione.Esempio di un codice C++ che genera un warning:
// example1.cpp#include<iostream> float multi(int a, int b) { return a*b; }; int main() { float a=2.5; int b=1; cout<<"a="<<a<<", b="<<b<<'\n'; cout<<"a*b="<<multi(a,b)<<'\n'; return 0; }In fase di compilazione apparira' il seguente warning:
example1.cpp: In function `int main ()': example1.cpp:12: warning: passing `float' for argument passing 1 of `multi (int, int)' example1.cpp:12: warning: argument to `int' from `float'Il messaggio ci avvisa che alla linea 12 del main() e' stato passato alla funzione multi un float invece che un int.
Esempio di un codice che genera un messaggio di errore:
// example1.cpp#include<iostream> float multi(int a, int b) { return a*b }; int main() { int a=2; int b=1; cout<<"a="<<a<<", b="<<b<<'\n'; cout<<"a/b="<<multi(a,b)<<'\n'; return 0; }Si noti che l' instruzione di return all' interno della funzione multi non termina con il ; .
A causa di questo grave errore la compilazione non puo' essere portata a termine:
example1.cpp: In function `float multi (int, int)': example1.cpp:5: parse error before `}' example1.cpp:5: warning: no return statement in function returning non-void
Per inibire tutti i messaggi di warinig usare l' opzione -w
g++ -w -o prova example1.cppPer usare il massimo livello di warning usare l' opzione -Wall
g++ -Wall -o prova example1.cpp
Compilare per effetture il debug
Se siete intenzionati ad effettuare il debug di un programma, utilizzate sempre l'opzione -g:
g++ -Wall -g -o pippo example1.cpp
L' opzione -g fa in modo che il programma eseguibile contenga informazioni supplementari che permettono al debugger di collegare le istruzioni in linguaggio macchina che si trovano nell'eseguibile alle righe del codice corrispondenti nei sorgenti C/C++.
Autopsia di un programma defunto Il seguente codice C++, wrong.cpp, genera un errore (nella fattispecie una divisione per 0) in fase di esecuzione che porta alla terminazione innaturale del programma
#include<iostream> int div(int a, int b) { return a/b; }; int main() { int a=2; int b=0; cout<<"a="<<a<<", b="<<b<<'\n'; cout<<"a/b="<<div(a,b)<<'\n'; return 0; }Compiliamo il file wrong_code.cpp
g++ -Wall -g -o wrong_program wrong_code.cppIl codice e' sintatticamente ineccepibile e verra' compilato senza problemi. In fase di esecuzione si verifica tuttavia una divisione per zero che causa la morte del programma.
./wrong_program a=2, b=0 Floating exception (core dumped)Linux genera nella directory corrente un file in cui scarica la memoria memoria assocciata al programma (core dump):
ls -sh total 132k 100k core 4.0k wrong_code.cpp 28k wrong_program*Il file core contiene l 'immagine della memoria (riferita al nostro programma) al momento dell'errore.
Possiamo effettuare l' autopsia del programma utilizzando il debugger GNU gdb
gdb wrong_program core ... Core was generated by `wrong_prog'. ... #0 0x080486a5 in div (a=2, b=0) at wrong_code.cpp:4 4 return a/b; (gdb) where #0 0x080486a5 in div (a=2, b=0) at wrong_code.cpp:4 #1 0x08048737 in main () at wrong_code.cpp:13 #2 0x400b1647 in __libc_start_main (main=0x80486b0 <main>, argc=1, ubp_av=0xbfffe614, init=0x80484e8 <_init>, fini=0x80487b0 <_fini>, rtld_fini=0x4000dcd4 <_dl_fini>, stack_end=0xbfffe60c) at ../sysdeps/generic/libc-start.c:129 (gdb) quitIl comando where di gdb ci informa che l' errore si e' verificato alla riga 4 del modulo wrong_code.cpp.
Esiste una versione con interfaccia grafica di gdb : kdbg
Ottimizzazione Il compilatore gcc consente di utilizzare diverse opzioni per ottenere un risultato più o meno ottimizzato. L'ottimizzazione richiede una potenza elaborativa maggiore, al crescere del livello di ottimizzazione richiesto. L' opzione -On ottimizza il codice, dove n é il livello di ottimizzazione. Il massimo livello di ottimizzazione allo stato attuale é il 3, quello generalmente più usato é 2. Quando non si deve eseguire il debug é consigliato ottimizzare il codice.
Opzione Descrizione -O, -O1 Ottimizzazione minima -O2 Ottimizzazione media -O3 Ottimizzazione massima -O0 Nessuna ottimizzazione Esempio di un codice chiaramente inefficiente
int main() { int a=10; int b=1; int c; for (int i=0; i<1e9; i++) { c=i+a*b-a/b; } return 0; }Confronto dei tempi di esecuzione in funzione di livelli di ottimizzazione crescente
Livello Tempo di esecuzione (secondi) O0 32.2 O1 5.4 O2 5.2 O3 5.2
Compilazione di un programma modulare
Un programma modulare e' un programma spezzettato in componenti piu' piccole con funzioni specifiche. La programmazione modulare e' piu' facile da comprendere e da correggere.
Nel seguito abbiamo un programma C++ composto da tre moduli: main.cpp, myfunc.cpp e myfunc.h .
// main.cpp #include<iostream> #include"myfunc.h" int main() { int a=6; int b=3; cout<<"a="<<a<<", b="<<b<<'\n'; cout<<"a/b="<<div(a,b)<<'\n'; cout<<"a*b="<<mul(a,b)<<'\n'; return 0; }
// myfunc.h int div(int a, int b); int mul(int a, int b);
// myfunc.cpp int div(int a, int b) { return a/b; }; int mul(int a, int b) { return a*b; };
- Per compilare usiamo
g++ -Wall -g -o prova main.cpp myfunc.cppSi noti che il file myfunc.h non appare nella riga di comando, verra' incluso dal gcc in fase di precompilazione.
Inclusione di librerie in fase di compilazione L'opzione -lnome_libreria compila utilizzando la libreria indicata, tenendo presente che, per questo, verrà cercato un file che inizia per lib, continua con il nome indicato e termina con .a oppure .so.
Modifichiamo i moduli myfunc.h e myfunc.cpp aggiungendo la funzione pot:
// myfunc.h int div(int a, int b); int mul(int a, int b); float pot(float a, float b);
// myfunc.cpp int div(int a, int b) { return a/b; }; int mul(int a, int b) { return a*b; };float pot(float a, float b) { return pow(a,b); }La compilazione pero' si interrompe
g++ -Wall -g -o prova main.cpp myfunc.cpp myfunc.cpp: In function `float pot (float, float)': myfunc.cpp:11: `pow' undeclared (first use this function) myfunc.cpp:11: (Each undeclared identifier is reported only once for each function it appears in.)La funzione pow e' contenuta nella libreria matematica math, dobbiamo allora aggiungere l'istruzione include nel modulo :
// myfunc.cpp#include<math.h> int div(int a, int b) { return a/b; }; int mul(int a, int b) { return a*b; }; float pot(float a, float b) { return pow(a,b); }e compilare con un link alla libreria libm.so
g++ -Wall -g -o prova main.cpp myfunc.cpp -lmDi default il compilatore esegue la ricerca della libreria nel direttorio standard /usr/lib/. Tramite l' opzione -L/nome_dir, e' possibile aggiunge la directory /nome_dir alla lista di direttori in cui gcc cerca le librerie in fase di linking.