È possibile elencare i simboli definiti automaticamente dalla propria
versione di gcc eseguendolo con l'opzione -v
. Ad esempio:
$ echo 'main(){printf("hello world\n");}' | gcc -E -v -
Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specs
gcc version 2.7.2
/usr/lib/gcc-lib/i486-box-linux/2.7.2/cpp -lang-c -v -undef
-D__GNUC__=2 -D__GNUC_MINOR__=7 -D__ELF__ -Dunix -Di386 -Dlinux
-D__ELF__ -D__unix__ -D__i386__ -D__linux__ -D__unix -D__i386
-D__linux -Asystem(unix) -Asystem(posix) -Acpu(i386)
-Amachine(i386) -D__i486__ -
Se si sta scrivendo del codice che utilizza delle caratteristiche specifiche per Linux, è una buona idea includere le parti non portabili in
#ifdef __linux__
/* ... altro codice ... */
#endif /* linux */
Si utilizzi __linux__
per questo scopo, non semplicemente
linux
. Sebbene anche il secondo sia definito, non è conforme a
POSIX.
La documentazione per le opzioni del compilatore è rappresentata dalla
info page di gcc (in Emacs, si utilizzi C-h i
quindi si
selezioni la voce 'gcc'). Il proprio distributore potrebbe non aver
incluso questa parte nel sistema, o la versione che si possiede potrebbe
essere vecchia; la cosa migliore da fare in questo caso consiste nello
scaricare l'archivio sorgente gcc da
ftp://prep.ai.mit.edu/pub/gnu o da uno dei suoi siti mirror.
La pagina di manuale gcc (gcc.1
) di solito è obsoleta. Tentando di
leggerla si troverà questo avvertimento.
L'output di gcc può essere ottimizzato aggiungendo -O
n
alla sua riga di comando, dove n rappresenta un intero opzionale. I
valori significativi di n, ed il loro esatto effetto, variano a
seconda della versione; tipicamente vanno da 0 (nessuna ottimizzazione) a
2 (molte ottimizzazioni) a 3 (moltissime ottimizzazioni).
Internamente, gcc le traduce in una serie di opzioni -f
e -m
. È
possibile vedere esattamente quale livello -O
si lega a ogni opzione
eseguendo gcc -v -Q
(Q
è un'opzione non documentata).
Ad esempio, -O2 sul sistema dell'autore produce questo risultato:
enabled: -fdefer-pop -fcse-follow-jumps -fcse-skip-blocks
-fexpensive-optimizations
-fthread-jumps -fpeephole -fforce-mem -ffunction-cse -finline
-fcaller-saves -fpcc-struct-return -frerun-cse-after-loop
-fcommon -fgnu-linker -m80387 -mhard-float -mno-soft-float
-mno-386 -m486 -mieee-fp -mfp-ret-in-387
L'utilizzo di un livello di ottimizzazione maggiore di quanto previsto per il proprio compilatore (ad esempio, -O6) avrà lo stesso risultato che utilizzare il livello più alto che è in grado di supportare. Tuttavia, la distribuzione di codice impostato per la compilazione in questo modo non è una buona idea - se in versioni future saranno incorporate ulteriori ottimizzazioni, potrebbe accadere che interrompano il proprio codice.
Gli utenti di gcc dalla versione 2.7.0 fino alla 2.7.2 dovrebbero notare
che esiste un errore in -O2. In particolare, '-fstrenght-reduction'
non funziona. È disponibile una patch per risolvere questo problema, che
necessita della ricompilazione del gcc, altrimenti è sufficiente assicurarsi
di usare sempre l'opzione -fno-strength-reduce
.
Esistono altre opzioni -m
che non sono attivate da nessun
-O
, e si dimostrano utili in molti casi. Le principali
sono -m386
e -m486
, che indicano a gcc di favorire
rispettivamente 386 o 486. Il codice compilato con una di queste due
opzioni funzionerà anche su macchine dell'altro tipo; il codice 486 è più
voluminoso, ma non è più lento se eseguito su 386.
Attualmente non esiste un'opzione -mpentium
o -m586
. Linus
suggerisce di utilizzare
-m486 -malign-loops=2 -malign-jumps=2 -malign-functions=2
, per
ottenere l'ottimizzazione del codice 486 ma senza i salti necessari per
l'allineamento (di cui il pentium non ha bisogno). Michael Meissner (di
Cygnus) dice:
La mia impressione è che-mno-strength-reduce
produca anche un codice più veloce sull'x86 (si noti che non mi sto riferendo all'errore relativo all'opzione '-fstrength-reduction': altra questione). Ciò è dovuto alla carenza di registri dell'x86 (ed il metodo di GCC di raggruppare i registri in registri sparsi piuttosto che in altri registri non aiuta molto). 'strenght-reduce
' ha come effetto l'utilizzo di registri addizionali per sostituire moltiplicazioni con addizioni. Sospetto anche che-fcaller-saves
possa causare una perdita di prestazioni.
Un'altra considerazione consiste nel fatto che-fomit-frame-pointer
possa o meno rappresentare un vantaggio. Da un lato, rende disponibile un altro registro per l'uso; tuttavia, il modo in cui x86 codifica il suo set di istruzioni comporta che gli indirizzi relativi di stack occupino più spazio rispetto agli indirizzi relativi di frame; di conseguenza, sarà disponibile ai programmi una quantità (leggermente) inferiore di Icache. Inoltre,-fomit-frame-pointer
implica il continuo aggiustamento del puntatore allo stack da parte del compilatore, dopo ogni chiamata, quando, con un frame, può lasciare che lo stack esegua un accumulo per alcune chiamate.
L'ultima parola su questo argomento viene ancora da Linus:
Se si desidera ottenere prestazioni ottimali, non credete alle mie parole, effettuate delle prove. Esistono molte opzioni nel compilatore gcc, e può darsi che un insieme particolare dia l'ottimizzazione migliore per la propria impostazione.
(Ovvero: "Errore interno del compilatore: cc1 ha ricevuto il segnale fatale 11").
Il segnale 11 è SIGSEGV, o 'segmentation violation'. Solitamente significa che il programma ha confuso i puntatori e ha tentato di scrivere su una porzione di memoria che non possedeva. Potrebbe trattarsi di un errore di gcc.
Tuttavia, gcc è ben verificato ed affidabile, nella maggior
parte dei casi. Utilizza anche un gran numero di strutture dati complesse,
e una grande quantità di puntatori. In breve, è il miglior tester di
RAM tra quelli disponibili comunemente. Se non è possibile replicare
l'errore - il sistema non si ferma nello stesso punto quando si riavvia
la compilazione - molto probabilmente si tratta di un problema legato
all'hardware (CPU, memoria, scheda madre o cache). Non lo si deve
considerare un errore del compilatore solo perché il proprio computer
supera i controlli di avvio del sistema o è in grado di eseguire Windows o
qualunque altro programma; questi 'test' sono comunemente ritenuti, a
ragione, di nessun valore. Comunque è sbagliato ritenere che si tratti di
un errore, perché una compilazione del kernel si blocca sempre durante
`make zImage
' - è logico che ciò accada: `make zImage
'
probabilmente compila più di 200 file. In genere si ricerca un
bug in un insieme più piccolo di così.
Se è possibile duplicare l'errore, e (ancora meglio) produrre un breve
programma che lo dimostra, è possibile inviarlo come report di errori
all'FSF, o alla mailing list di linux-gcc
. Si faccia riferimento
alla documentazione di gcc per i dettagli relativi alle informazioni
effettivamente necessarie.
È stato detto che, in questi giorni, se non può essere portato a Linux, allora è qualcosa che non vale la pena di possedere. :-)
In generale, sono necessarie solo piccole modifiche per ottenere la
conformità POSIX al 100% di Linux. Sarebbe anche molto utile restituire
agli autori ogni modifica al codice in modo che, in futuro, si possa
ottenere un eseguibile funzionante tramite il solo comando 'make
'.
È possibile compilare il proprio programma con -I/usr/include/bsd
ed eseguire il link con -lbsd
(ossia, aggiungere
-I/usr/include/bsd
a CFLAGS
e -lbsd
alla riga
LDFLAGS
nel proprio Makefile
). Non è più necessario aggiungere
-D__USE_BSD_SIGNAL
se si desidera un comportamento dei segnali di
tipo BSD, dal momento che questa caratteristica viene ottenuta
automaticamente quando si ha -I/usr/include/bsd
e l'include
<signal.h>
.
SIGBUS
, SIGEMT
,SIGIOT
, SIGTRAP
, SIGSYS
ecc) Linux è conforme a POSIX. Quelli elencati nel seguito sono segnali non definiti in POSIX - ISO/IEC 9945-1:1990 (IEEE Std 1003.1-1990), paragrafo B.3.3.1.1:
"I segnali SIGBUS, SIGEMT, SIGIOT, SIGTRAP, e SIGSYS sono stati omessi da POSIX.1 perché il loro comportamento dipende dall'implementazione e non è stato possibile classificarlo adeguatamente. Implementazioni conformi potranno contenere questi segnali, ma devono documentare le circostanze in cui sono rilasciati ed elencare ogni restrizione riguardante il loro rilascio."
Il modo più economico e scadente di gestire la cosa consiste nel ridefinire
questi segnali in SIGUNUSED
. Il modo corretto consiste
nell'inserire il codice che li gestisce in appropriati #ifdef
#ifdef SIGSYS
/* ... codice SIGSYS non-posix .... */
#endif
GCC è un compilatore ANSI; una grande quantità di codice esistente non è
ANSI. Non c'è molto da fare per risolvere questo problema, oltre
all'aggiungere -traditional
alle opzioni del compilatore. Si invita a
consultare l'info page di gcc.
Si noti che -traditional
ha altri effetti oltre a cambiare il
linguaggio accettato da gcc. Ad esempio, attiva
-fwritable-strings
, che sposta le stringhe costanti nello spazio
dati (dallo spazio di codice, dove non possono essere scritte).
Questo aumenta l'occupazione di memoria del programma.
Uno dei problemi più frequenti consiste nel fatto che alcune funzioni
comuni sono definite come macro negli header file di Linux e il
preprocessore si rifiuterà di eseguire il parsing di definizioni
prototipo simili. I più comuni sono atoi()
e atol()
.
sprintf()
Soprattutto quando si esegue il porting da SunOS, è necessario essere
consapevoli del fatto che sprintf(string, fmt, ...)
restituisce
un puntatore a stringhe, mentre Linux (seguendo ANSI) restituisce il
numero di caratteri che sono stati messi nella stringa.
fcntl
e affini. Quali sono le definizioni di FD_*
? Le definizioni si trovano in <sys/time.h>
. Se si sta
utilizzando fcntl
, probabilmente si vorrà includere anche
<unistd.h>
.
In genere, la pagina di manuale di una funzione elenca gli
#include
necessarie nella sua sezione SYNOPSIS.
select()
. Programmi in busy-waiting Una volta, il parametro timeout di select()
era utilizzato a
sola lettura. Già allora, le pagine di manuale avvertivano:
select() dovrebbe probabilmente restituire il tempo rimanente dal timeout originale, se esistente, modificando il valore del tempo. Questo potrà essere implementato in versioni future del sistema. Pertanto, sarebbe sbagliato ritenere che il puntatore al timeout non sarà modificato dalla chiamata select().
Ora il futuro è arrivato. Di ritorno da una select()
, l'argomento
di timeout sarà impostato al tempo rimanente che si sarebbe atteso se i
dati non fossero arrivati. Se non è arrivato alcun dato, il valore sarà
zero, e le chiamate future utilizzando la stessa struttura timeout
eseguiranno immediatamente il return.
Per rimediare, in caso di errore, si metta il valore di timeout nella
struttura ogni volta che si chiama select()
. Si modifichi il
codice come:
struct timeval timeout; timeout.tv_sec = 1; timeout.tv_usec = 0; while (some_condition) select(n,readfds,writefds,exceptfds,&timeout);
in
struct timeval timeout; while (some_condition) { timeout.tv_sec = 1; timeout.tv_usec = 0; select(n,readfds,writefds,exceptfds,&timeout); }
Alcune versioni di Mosaic evidenziavano questo problema. La velocità dell'animazione del globo rotante era inversamente correlata alla velocità con cui i dati giungevano dalla rete!
Quando un programma viene interrotto utilizzando Ctrl-Z
e poi
viene riavviato - o in altre situazioni che generano dei segnali:
interruzione con Ctrl-C
, terminazione di un processo figlio ecc. -
si ottengono messaggi del tipo "interrupted system call" o "write:
unknown error".
I sistemi POSIX controllano la presenza di segnali più frequentemente rispetto a sistemi UNIX più vecchi. Linux può eseguire dei gestori di segnali:
select()
, pause()
, connect()
,
accept()
, read()
su terminali, su socket, su
pipe o su file in /proc
, write()
su terminali,
su socket, su pipe o sulla line printer; open()
su FIFO, su PTY o su linee seriali, ioctl()
su terminali;
fcntl()
con il comando F_SETLKW
; wait4()
,
syslog()
, ogni operazione TCP o NFS.Per altri sistemi operativi potrebbe essere necessario includere in questa
lista le chiamate di sistema creat()
, close()
,
getmsg()
, putmsg()
, msgrcv()
,
msgsnd()
, recv()
, send()
, wait()
,
waitpid()
, wait3()
, tcdrain()
,
sigpause()
, semop()
.
Se un segnale (per il quale il programma ha installato un gestore) avviene
durante una chiamata di sistema, viene chiamato il gestore. Quando il
gestore restituisce il controllo (alla chiamata di sistema), essa rileva
che è stata interrotta e restituisce immediatamente -1 e
errno = EINTR
. Il programma non si aspetta che questo accada,
pertanto si blocca.
È possibile scegliere tra due alternative, per rimediare.
SA_RESTART
ai flag sigaction
. Per esempio, modificare
signal (sig_nr, my_signal_handler);
signal (sig_nr, my_signal_handler); { struct sigaction sa; sigaction (sig_nr, (struct sigaction *)0, &sa); #ifdef SA_RESTART sa.sa_flags |= SA_RESTART; #endif #ifdef SA_INTERRUPT sa.sa_flags &= ~ SA_INTERRUPT; #endif sigaction (sig_nr, &sa, (struct sigaction *)0); }
EINTR
personalmente
riguardo a read()
, write()
, ioctl()
,
select()
, pause()
e connect()
. Si veda sotto.
EINTR
esplicitamente.Seguono due esempi per read()
e ioctl()
.
Una parte di codice originale utilizzante read()
int result; while (len > 0) { result = read(fd,buffer,len); if (result < 0) break; buffer += result; len -= result; }
diventa
int result; while (len > 0) { result = read(fd,buffer,len); if (result < 0) { if (errno != EINTR) break; } else { buffer += result; len -= result; } }
e una parte di codice utilizzante ioctl()
int result; result = ioctl(fd,cmd,addr);
diventa
int result; do { result = ioctl(fd,cmd,addr); } while ((result == -1) && (errno == EINTR));
Si noti che in alcune versioni di Unix BSD il comportamento predefinito
consiste nel riavviare le chiamate di sistema. Per ottenere l'interruzione
delle chiamate di sistema è necessario utilizzare i flag
SV_INTERRUPT
o SA_INTERRUPT
.
GCC ha una visione ottimistica dei suoi utenti, considerando le costanti stringa esattamente quello che sono - delle costanti. Pertanto, le memorizza nell'area del codice, dove possono essere inserite ed estratte dall'immagine di disco del programma (invece di occupare uno swapspace). Ne consegue che ogni tentativo di riscriverle causerà 'segmentation fault'.
Questo può causare dei problemi a vecchi programmi che, per esempio
eseguono una chiamata mktemp()
con una stringa costante come
argomento. mktemp()
tenta di riscrivere il suo argomento.
Per correggere,
-fwritable-strings
, per fare in modo
che gcc posizioni le costanti nello spazio dati, oppure
strcpy
dei dati in essa prima della
chiamata.execl()
fallisce? Probabilmente accade perché viene eseguita in modo errato. Il primo
argomento per execl
è il nome del programma da eseguire.
Il secondo e i successivi diventano l'array argv
del programma
che si sta chiamando. Ricordare che: argv[0]
viene
impostato anche per un programma eseguito senza argomenti. Pertanto si
dovrebbe scrivere
execl("/bin/ls","ls",NULL);
e non solo
execl("/bin/ls", NULL);
L'esecuzione del programma senza nessun argomento è interpretata come un
invito a stampare le sue dipendenze a librerie dinamiche, almeno
utilizzando a.out
. ELF si comporta diversamente.
(Se si desidera questa informazione di libreria, esistono interfacce
più semplici; si veda il paragrafo relativo al caricamento dinamico, o la
pagina di manuale per ldd
).