Vous pouvez trouver quels symboles votre version de gcc définit
automatiquement en le lançant avec l'option -v
.
Par exemple cela donne ça chez moi :
$ echo 'main(){printf("Bonjour !\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__ -
Si vous écrivez du code qui utilise des spécificités Linux, il
est souhaitable d'implémenter le code non portable de la manière suivante
#ifdef __linux__
/* ... code linux ... */
#endif /* linux */
Utilisez __linux__
pour cela, et pas linux
.
Bien que cette macro soit définie, ce n'est pas une spécification POSIX.
La documentation des options de compilation se trouve dans les
pages info de gcc (sous Emacs, utilisez C-h i
puis
sélectionnez l'option `gcc'). Votre distribution peut ne pas
avoir installé la documentation ou bien vous pouvez en avoir une
ancienne. Dans ce cas, la meilleure chose à faire est de récupérer
les sources de gcc depuis
ftp://prep.ai.mit.edu/pub/gnu ou
l'un des ses nombreux miroirs dont
ftp://ftp.ibp.fr/pub/gnu.
La page de manuel gcc (gcc.1
) est en principe, complètement
dépassée. Cela vous met en garde si vous désirez la consulter.
gcc peut réaliser un certain nombre d'optimisations sur le code généré
en ajoutant l'option -O
n à la ligne de commandes, où
n est un chiffre. La valeur de n, et son effet exact,
dépend de la version de gcc, mais s'échelonne normalement entre
0 (aucune optimisation) et 2 (un certain nombre) ou 3 (toutes les
optimisations possibles).
En interne, gcc interprète les options telles que -f
et -m
.
Vous pouvez voir exactement ce qu'effectue le niveau spécifié dans
l'option -O
en lançant gcc avec l'option -v
et l'option (non documentée)
-Q
. Par exemple, l'option -O2
, effectue les opérations
suivantes sur ma machine :
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
Utiliser un niveau d'optimisation supérieur à celui que le compilateur
supporte (par exemple -O6
) aura le même effet qu'utiliser le plus
haut niveau géré. Distribuer du code où la compilation est configurée de
cette manière est une très mauvaise idée -- si d'autres optimisations
sont incorporées dans de versions futures, vous (ou d'autres utilisateurs)
pouvez vous apercevoir que cela ne compile plus, ou bien que le code
généré ne fait pas les actions désirées.
Les utilisateurs de gcc 2.7.0 à 2.7.2 devraient noter qu'il y a un
bogue dans l'option -O2
. Plus précisément, la
strength reduction ne fonctionne pas. Un patch a été
implémenté pour résoudre ce problème, mais vous devez alors
recompiler gcc. Sinon, vous devrez toujours compiler avec l'option
-fno-strength-reduce
.
Il existe d'autres options -m
qui ne sont pas positionnées
lors de l'utilisation de -O
mais qui sont néanmoins utiles dans certains
cas. C'est le cas pour les options -m386
et -m486
,
qui indiquent à gcc de générer un code plus ou moins optimisé
pour l'un ou l'autre type de processeur. Le code continuera à fonctionner
sur les deux processeurs. Bien que le code pour 486 soit plus important, il
ne ralentit pas l'exécution du programme sur 386.
Il n'existe pas actuellement de -mpentium
ou -m586
. Linus
a suggéré l'utilisation des options
-m486 -malign-loops=2 -malign-jumps=2 -malign-functions=2
,
pour exploiter les optimisations du 486 tout en perdant de la place
due aux problèmes d'alignements (dont le Pentium n'a que faire).
Michael Meissner (de Cygnus) nous dit :
« Mon avis est que l'option-mno-strength-reduce
permet d'obtenir un code plus rapide sur un x86 (nota : je ne parle pas du bogue strength reduction, qui est un autre problème). Cela s'explique en raison du peu de registres dont disposent ces processeurs (et la méthode de GCC qui consiste à grouper les registres dans l'ordre inverse au lieu d'utiliser d'autres registres n'arrange rien). La strength reduction consiste en fait à rajouter des registres pour remplacer les multiplications par des additions. Je suspecte également-fcaller-saves
de ne pas arranger la situation. »
Une autre idée est que-fomit-frame-pointer
n'est pas obligatoirement une bonne idée. D'un côté, cela peut signifier qu'un autre registre est disponible pour une allocation. D'un autre côté, vue la manière dont les processeurs x86 codent leur jeu d'instruction, cela peut signifier que la pile des adresses relatives prend plus de place que les adresses de fenêtres relatives, ce qui signifie en clair que moins de cache est disponible pour l'exécution du processus. Il faut préciser que l'option-fomit-frame-pointer
, signifie que le compilateur doit constamment ajuster le pointeur de pile après les appels, alors qu'avec une fenêtre, il peut laisser plusieurs appels dans la pile.
Le mot final sur le sujet provient de Linus :
Remarquez que si vous voulez des performances maximales, ne me croyez pas : testez ! Il existe tellement d'options de gcc, et il est possible que cela ne soit une réelle optimisation que pour vous.
Internal compiler error: cc1 got fatal signal 11
Signal 11 correspond au signal SIGSEGV, ou bien segmentation violation. Normalement, cela signifie que le programme s'est mélangé les pointeurs et a essayé d'écrire là où il n'en a pas le droit. Donc, cela pourrait être un bug de gcc.
Toutefois, gcc est un logiciel assez testé et assez remarquable de ce côté.
Il utilise un grand nombre de structures de données complexes, et un
nombre impressionnant de pointeurs. En résumé, c'est le plus pointilleux
des testeurs de mémoire existants. Si vous n'arrivez pas à reproduire le
bogue
--- si cela ne s'arrête pas au même endroit lorsque vous retentez la
compilation --- c'est plutôt un problème avec votre machine (processeur,
mémoire, carte mère ou bien cache). N'annoncez pas la découverte
d'un nouveau bogue si votre ordinateur traverse tous les tests du BIOS,
ou s'il fonctionne correctement sous Windows ou autre : ces tests
ne valent rien. Il en va de même si le noyau s'arrête lors du
`make zImage
' ! `make zImage
'
doit compiler plus de 200 fichiers, et il en faut bien moins pour arriver
à faire échouer une compilation.
Si vous arrivez à reproduire le bogue et (mieux encore) à écrire un petit programme qui permet de mettre en évidence cette erreur, alors vous pouvez envoyer le code soit à la FSF, soit dans la liste linux-gcc. Consultez la documentation de gcc pour plus de détails concernant les informations nécessaires.
Cette phrase a été dite un jour : si quelque chose n'a pas été porté vers Linux alors ce n'est pas important de l'avoir :-).
Plus sérieusement, en général seules quelques modifications mineures sont nécessaires car Linux répond à 100% aux spécifications POSIX. Il est généralement sympathique d'envoyer à l'auteur du programme les modifications effectuées pour que le programme fonctionne sur Linux, pour que lors d'une future version, un `make' suffise pour générer l'exécutable.
bsd_ioctl
, daemon
et<sgtty.h>
) Vous pouvez compiler votre programme avec l'option
-I/usr/include/bsd
et faire l'édition de liens avec -lbsd
(en ajoutant -I/usr/include/bsd
à la ligne CFLAGS
et -lbsd
à la ligne LDFLAGS
dans votre fichier
Makefile
). Il est également nécessaire de ne pas
ajouter -D__USE_BSD_SIGNAL
si vous voulez
que les signaux BSD fonctionnent car vous les avez inclus automatiquement
avec la ligne
-I/usr/include/bsd
et en incluant le fichier d'en-tête
<signal.h>
.
SIGBUS
, SIGEMT
, SIGIOT
, SIGTRAP
, SIGSYS
, etc.) Linux respecte les spécifications POSIX. Ces signaux n'en font pas partie (cf. ISO/IEC 9945-1:1990 - IEEE Std 1003.1-1990, paragraphe B.3.3.1.1) :
« Les signaux SIGBUS, SIGEMT, SIGIOT, SIGTRAP, et SIGSYS ont été omis de la norme POSIX.1 car leur comportement est dépendant de l'implémentation et donc ne peut être répertorié d'une manière satisfaisante. Certaines implémentations peuvent fournir ces signaux mais doivent documenter leur effet »
La manière la plus élégante de régler ce problème est de
redéfinir ces signaux à SIGUNUSED
. La manière normale de
procéder est d'entourer le code avec les #ifdef
appropriés :
#ifdef SIGSYS
/* ... code utilisant les signaux non posix .... */
#endif
GCC est un compilateur ANSI, or il existe beaucoup de code qui ne soit pas ANSI.
Il n'y a pas grand chose à faire, sauf rajouter l'option
-traditional
lors de la compilation. Il effectue certaines
vérifications supplémentaires. Consultez les pages info gcc.
Notez que l'option -traditional
a pour unique effet de changer la forme
du langage accepté par gcc. Par exemple, elle active l'option
-fwritable-strings
, qui déplace toutes les chaînes de caractères
vers l'espace de données (depuis l'espace de texte, où elle ne
peuvent pas être modifiées). Ceci augmente la taille de la mémoire
occupée par le programme.
Un des problèmes fréquents se produit lorsque certaines fonctions
standards sont définies comme macros dans les fichiers d'en-tête
de Linux et le préprocesseur refusera de traiter
des prototypes identiques. Par exemple, cela peut arriver
avec atoi()
et atol()
.
sprintf()
Parfois, soyez prudent lorsque vous effectuez un portage à partir
des sources de programmes fonctionnant sous SunOs, surtout avec
la fonction sprintf(string, fmt, ...)
car elle renvoie
un pointeur sur la chaîne de caractères alors que Linux (suivant la norme ANSI)
retourne le nombre de caractères recopiés dans la chaîne de caractères.
fcntl
et ses copains. Où se trouve la définition de FD_*
et compagnie ? Dans <sys/time.h>
. Si vous utilisez
fcntl
vous voudrez probablement inclure
<unistd.h>
également, pour avoir le prototype de la fonction.
D'une manière générale, la page de manuel pour une fonction donne la liste des fichiers d'en-tête à inclure.
select()
. Les programmescommencent dans un état d'attente active A une certaine époque, le paramètre timeout de la fonction
select()
était utilisé en lecture seule. C'est pourquoi la page
de manuel comporte une mise en garde :
select() devrait retourner normalement le temps écoulé depuis le
timeout initial, s'il s'est déclenché, en modifiant la valeur pointée par
le paramètre time
. Cela sera peut-être implémenté dans les
versions ultérieures du système. Donc, il n'est pas vraiment prudent de
supposer que les données pointées ne seront pas modifiées lors de l'appel
à select().
Mais tout arrive avec le temps ! Lors d'un retour de
select()
, l'argument timeout
recevra le
temps écoulé depuis la dernière réception de données. Si aucune
donnée n'est arrivée, la valeur sera nulle, et les futurs
appels à cette fonction utilisant le même timeout
auront pour résultat un retour immédiat.
Pour résoudre le problème, il suffit de mettre la valeur timeout
dans la structure à chaque appel de select()
.
Le code initial était
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
while (some_condition)
select(n,readfds,writefds,exceptfds,&timeout);
et doit devenir :
struct timeval timeout;
while (some_condition)
{
timeout.tv_sec = 1;
timeout.tv_usec = 0;
select(n,readfds,writefds,exceptfds,&timeout);
}
Certaines versions de Mosaic étaient connues à une certaine époque pour avoir ce problème.
La vitesse de rotation du globe terrestre était inversement proportionnelle à la vitesse de transfert des données !
Lorsqu'un processus est arrêté avec un Ctrl-Z et relancé - ou bien lorsqu'un autre signal est déclenché dans une situation différente : par exemple avec un Ctrl-C, la terminaison d'un processus, etc, on dit qu'il y a « interruption d'un appel système » , ou bien « write : erreur inconnue » ou des trucs de ce genre.
Les systèmes POSIX vérifient les signaux plus souvent que d'autres Unix plus anciens. Linux peux lancer les gestionnaires de signaux :
select()
, pause()
, connect()
,
accept()
, read()
sur des terminaux, des sockets, des pipes
ou des fichiers situés dans /proc
, write()
sur des
terminaux, des sockets, des pipes ou des imprimantes, open()
sur des FIFOs, des lignes PTYs ou séries,
ioctl()
sur des terminaux, fcntl()
avec la commande
F_SETLKW
, wait4()
, syslog()
, et toute opération
d'ordre TCP ou NFS. Sur d'autres systèmes d'exploitation, il est possible que vous
ayez à inclure dans cette catégorie les appels systèmes suivants :
creat()
, close()
, getmsg()
, putmsg()
,
msgrcv()
, msgsnd()
, recv()
, send()
,
wait()
, waitpid()
, wait3()
, tcdrain()
,
sigpause()
, semop()
.
Si un signal (que le programme désire traiter) est lancé pendant
l'exécution d'un appel système, le gestionnaire est lancé. Lorsque
le gestionnaire du signal se termine, l'appel système détecte qu'il a été
interrompu et se termine avec la valeur -1 et errno = EINTR
.
Le programme n'est pas forcément au courant de ce qui s'est passé et
donc s'arrête.
Vous pouvez choisir deux solutions pour résoudre ce problème.
(1)Dans tout gestionnaire de signaux que vous mettez en place, ajoutez
l'option SA_RESTART
au niveau de sigaction. Par exemple,
modifiez
signal (signal_id, mon_gestionnaire_de_signaux);
en
signal (signal_id, mon_gestionnaire_de_signaux);
{
struct sigaction sa;
sigaction (signal_id, (struct sigaction *)0, &sa);
#ifdef SA_RESTART
sa.sa_flags |= SA_RESTART;
#endif
#ifdef SA_INTERRUPT
sa.sa_flags &= ~ SA_INTERRUPT;
#endif
sigaction (signal_id, &sa, (struct sigaction *)0);
}
Notez que lors de certains appels systèmes vous
devrez souvent regarder si errno
n'a pas été positionnée à
EINTR
par vous même comme avec read()
, write()
,
ioctl()
, select()
, pause()
et connect()
.
(2) A la recherche de EINTR
:
Voici deux exemples avec read()
et ioctl()
,
Voici le code original utilisant read()
int result;
while (len> 0)
{
result = read(fd,buffer,len);
if (result < 0)
break;
buffer += result;
len -= result;
}
et le nouveau code
int result;
while (len> 0)
{
result = read(fd,buffer,len);
if (result < 0)
{
if (errno != EINTR)
break;
}
else
{
buffer += result;
len -= result;
}
}
Voici un code utilisant ioctl()
int result;
result = ioctl(fd,cmd,addr);
et cela devient
int result;
do
{
result = ioctl(fd,cmd,addr);
}
while ((result == -1) && (errno == EINTR));
Il faut remarquer que dans certaines versions d'Unix de type BSD
on a l'habitude de relancer l'appel système. Pour récupérer les interruptions
d'appels systèmes, vous devez utiliser les options
SV_INTERRUPT
ou SA_INTERRUPT
.
GCC a une vue optimiste en ce qui concerne ses utilisateurs, en croyant qu'ils respectent le fait qu'une chaîne dite constante l'est réellement. Donc, il les range dans la zone texte(code) du programme, où elles peuvent être chargées puis déchargées à partir de l'image binaire de l'exécutable située sur disque (ce qui évite d'occuper de l'espace disque). Donc, toute tentative d'écriture dans cette chaîne provoque un « segmentation fault ».
Cela peut poser certains problèmes avec d'anciens codes, par exemple
ceux qui utilisent la fonction mktemp()
avec une chaîne constante
comme argument. mktemp()
essaye d'écrire dans la chaîne passée
en argument.
Pour résoudre ce problème,
-fwritable-strings
pour indiquer
à gcc de mettre les chaînes constantes dans l'espace de donnéesexecl()
échoue ? Tout simplement parce que vous l'utilisez mal. Le premier argument
d'execl
est le programme que vous désirez exécuter. Le second et ainsi
de suite sont en fait le éléments du tableau argv
que vous appelez.
Souvenez-vous que argv[0]
est traditionnellement fixé même si
un programme est lancé sans argument. Vous devriez donc écrire :
execl("/bin/ls","ls",NULL);
et pas
execl("/bin/ls", NULL);
Lancer le programme sans argument est considéré comme étant une demande d'affichage des bibliothèques dynamiques associées au programme, si vous utilisez le format a.out. ELF fonctionne d'une manière différente.
(Si vous désirez ces informations, il existe des outils plus simples;
consultez la section sur le chargement dynamique, ou la page de manuel
de ldd
).