Erreur de compilation : ld returned 1 exit status

Le problème exposé dans ce sujet a été résolu.

Bonjour,

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 :

#include <stdio.h>
#include "morse.h"


int main(void) {
    return 0;
}
main.c
#ifndef MORSE_H
#define MORSE_H

char* Morse_alphabet[] = {
        
    ['0'] = "-----",
    ['1'] = ".----",
    ['2'] = "..---"
};

void Morse_test(void);

#endif
morse.h
#include "morse.h"
#include <string.h>

void Morse_test(void) {

}
morse.c

J’utilise les deux commandes suivantes pour compiler mon programme dans le terminal de Geany (sous Linux) :

gcc -c morse.c
gcc morse.o main.c -o main

D’où pourrait venir l’erreur ?

+0 -0

Salut,

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.

+2 -0

Merci pour la réponse ! :)

@Taurre

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 ?

@Karnaj

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 ?

Exemple :

#include <stdio.h>
#include "morse.h"

int main(void)
{
    printf("Valeur de i : %d\n", i);
    return 0;
}
main.c
#ifndef MORSE_H
#define MORSE_H

// Pas de mot-clef extern
int i;

#endif
morse.h
#include "morse.h"

int i = 42;
morse.c

Il compile car i est défini, mais attention, il ne vaut pas 42 dans main.c !

La commande #include va inclure le fichier avant compilation (c’est fait par le préprocesseur). Donc tes fichiers compilés sont en fait :

<contenue de stdio.h, que tu va trouver dans /usr/include/stdio.h sous Linux, par exemple>
#ifndef MORSE_H
#define MORSE_H

// Pas de mot-clef extern
int i;

#endif

int main(void)
{
    printf("Valeur de i : %d\n", i);
    return 0;
}
main.c

Et ça, ça compile très bien sans extern. Mais ça ne va pas donner 42.

+0 -0

Et ça, ça compile très bien sans extern. Mais ça ne va pas donner 42.

C’est étrange, parce qu’en compilant puis en exécutant l’exemple, il affiche :
Valeur de i : 42

Peut-être est-ce dû à la manière dont j’ai compilé le programme ?

gcc -c morse.c
gcc morse.o main.c -o main

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. :D

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

+0 -0

Salut,

@Taurre

Dans le tutoriel C, par apport à ce paragraphe, je me demandais si vous pouviez y inclure un exemple avec les variables ?

Green

Effectivement, cela serait une bonne idée, je note, merci. :)

De ce que j’ai compris, le fichier d’entête ne sert qu’aux déclarations, y compris pour les variables.

Green

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).

Par conséquent, si j’ai bien compris, il est interdit d’initialiser une variable dans un fichier .h ?

Green

Il est grandement préférable de ne pas le faire, mais ce n’est pas interdit, non.

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 ? […]

Green

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;


int main(void)
{
    printf("Valeur de i : %d\n", i);
    return 0;
}
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> */

extern int i;


int
main(void)
{
    printf("Valeur de i : %d\n", i);
    return 0;
}

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. ^^

+1 -0
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