J’essaye de compiler un programme mais cela échoue avec l’erreur suivante :
/tmp/ccfNQRbo.o:(.data.rel.local+0x0) : définitions multiples de « Morse_alphabet »
morse.o:(.data.rel.local+0x0) : défini pour la première fois ici
collect2: error: ld returned 1 exit status
Pourtant, ma variable Morse_alphabet est bien définie une et une seule fois dans le code :
J’utilise les deux commandes suivantes pour compiler mon programme dans le terminal de Geany (sous Linux) :
Morse_alphabet est défini dans un fichier d’en-tête qui est inclus dans main.c et dans morse.c (donc deux fois). Déclare la variable dans morse.c et utilise extern dans morse.h.
Dans le tutoriel C, par apport à ce paragraphe, je me demandais si vous pouviez y inclure un exemple avec les variables ?
De ce que j’ai compris, le fichier d’entête ne sert qu’aux déclarations, y compris pour les variables.
Par conséquent, si j’ai bien compris, il est interdit d’initialiser une variable dans un fichier .h ?
Si j’ai bien compris, le mot-clef extern permet de dire au programme que la variable est définie dans un autre fichier. Mais je ne comprends pas très bien pourquoi le code compile, même sans utiliser ce mot ?
Peut-être est-ce dû à la manière dont j’ai compilé le programme ?
Compilé avec
gcc main.c -o main
Ça compile, et ça donne 0.
Mais effectivement, si tu ajoutes morse.o, ça marche toujours et ça affiche 42. J’aurai parié le contraire (conflit de nom ou similaire), mais en fait ça marche très bien. Je te laisse imaginer à quel point il devient facile de se perdre dans son code en faisant ça.
Et en C++, ça ne marche pas…
ld : morse.o:(.data+0x0) : définitions multiples de « i »; main.o:(.bss+0x0) : défini pour la première fois ici
collect2: error: ld returned 1 exit status
Effectivement, cela serait une bonne idée, je note, merci.
Yep, en tous les cas c’est comme cela qu’il est préférable de faire, mais cela ne signifie pas que c’est interdit par le langage même (le ton du tuto est parfois volontairement assertif pour éviter certains comportements).
Il est grandement préférable de ne pas le faire, mais ce n’est pas interdit, non.
Alors, pour les détails farfelus, j’ai rédigé un tuto sur les identificateurs qui explique cela en long et en large (la section qui t’intéresse plus spécifiquement est « liaisons et définitions »). Pour la réponse rapide et concise : la compilation passe parce que tu es sous Linux et que tu n’utilises pas l’option -fno-common. Pour la réponse longue : du point de vue de la norme et après passage du préprocesseur, voici que tu obtiens.
main.c
/* Contenu de <stdio.h> */int i = 0; /* Définition implicite */int i;
intmain(void){
printf("Valeur de i : %d\n", i);
return0;
}
morse.c
int i;
int i = 42;
Dans le cas du fichier morse.c tu as une définition de la variable i (lui affectant la valeur 42) et une définition potentielle (techniquement c’est une déclaration, mais le terme est important pour la suite) de la variable i (venant du fichier d’en-tête morse.h). Ici, rien de particulier : tu déclares une variable i puis tu la définis. Tu aurais pu faire d’une pierre deux coups en te contentant de la définition, mais il n’y a rien de problématique.
Dans le fichier main.c, en revanche, tu as une définition potentielle de la variable i (provenant également du fichier morse.h) et elle est seule. Or, dans un tel cas, la norme précise qu’une définition implicite est ajoutée (définition qui initialise la variable à zéro).
Du coup, tu te retrouves avec une variable i utilisable dans tout le programme (on dit qu’elle a une liaison externe), mais qui est définie deux fois, ce qui est interdit. Cependant, sous Linux et BSD, ce comportement est toléré par défaut (il faut employer l’option -fn-common pour supprimer cette tolérance) et les définitions potentielles sont considérées comme des déclarations. En gros, ton fichier main.c ressemble alors à ça :
/* Contenu de <stdio.h> */externint i;
intmain(void){
printf("Valeur de i : %d\n", i);
return0;
}
Ce qui devient correct puisque tu n’as plus une seconde définition.
Note : c’est pour éviter cette confusion que l’option -fno-common est utilisée dans l’alias fourni par le tuto.
Connectez-vous pour pouvoir poster un message.
Connexion
Pas encore membre ?
Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte