IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

Problème d'inclusions multiples


Sujet :

C++

  1. #1
    Membre habitué
    Profil pro
    Développeur informatique
    Inscrit en
    Février 2008
    Messages
    289
    Détails du profil
    Informations personnelles :
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Février 2008
    Messages : 289
    Points : 151
    Points
    151
    Par défaut Problème d'inclusions multiples
    Bonjour,
    j'ai créé un fichier def_constantes.h pour regrouper tout un ensemble de constantes dans un namespace.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #ifndef DEF_CONSTANTES_H
    #define DEF_CONSTANTES_H
     
    #include <QString>
    namespace Centrale
    {
        QString ip_centrale("192.168.0.100");
        QString tag_t1 = "<Temperature1>";
    }
    #endif
    J'inclus ce fichier dans mainwindow.cpp et un fichier de tests stuff.cpp:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include "stuff.h"
    #include "def_constantes.h"
     
    void Stuff::extraire()
    {
        int position_start;
     
        position_start = buff.find(Centrale::tag_t1.toStdString());   //buff est un attribut de la classe Centrale
    }
    Et j'ai systématiquement le message:
    :-1: erreur : stuff.o:(.bss+0x58): multiple definition of `Teracom::tag_t1'; mainwindow.o:(.bss+0x58): first defined here
    Est-ce que j'écris mal mon fichier def_constantes.h ou alors les conditions de garde ne jouent pas leur rôle?
    Je ne comprend pas ce qui se passe, si quelqu'un a une idée?

    Merci.

  2. #2
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,

    Pour que la réponse à ta question soit compréhensible, il faut déjà comprendre comment se passe la compilation.

    Pour faire au plus simple, on va dire que la compilation travaille avec des "unités de compilation"; chaque unité de compilation étant le résultat de
    • un fichier d'implémentation (un fichier *.cpp)
    • l'inclusion récursive de tous les fichiers d'en-têtes (les fichiers *.h / *.hpp) requis par le fichier d'implémentation

    Ainsi, si j'ai un fichier d'implémentation fichier.cpp et que, au niveau de ce fichier j'inclus les fichiers a.hpp et d.hpp, mais que dans le même temps, a.hpp inclut b.hpp, qui inclut lui-même c.hpp, l'unité de compilation correspondra au conteneu de
    • a.hpp
    • b.hpp
    • c.hpp
    • d.hpp et
    • fichier.cpp


    De plus, il faut savoir que chaque unité de compilation va être compilée séparément et de manière tout à fait isolée, dans le sens où le compilateur va "oublier" systématiquement tout le travail effectué pour chaque unité de compilation qu'il aura à traiter.

    Ainsi, si le compilateur a deux fichiers d'implémentation à traiter, il va créer une première unité de compilation correspondant au premier fichier, la traiter pour générer le code binaire qui sera exécuté par le processeur, et "oublier tout ce qu'il a fait" pour passer au fichier suivant.

    Et même le fait qu'il puisse -- éventuellement -- traiter plusieurs fichiers de manière simultanée ne changera rien à l'histoire: c'est vraiment un coup où la main droite qui traite le premier fichier ignore complètement ce que fait la main gauche qui est (peut-être) occupée à traiter le deuxième

    Maintenant que l'on a cela bien en tête, on comprend facilement que les gardes anti inclusion ne pourront fonctionner qu'au niveau de l'unité de compilation.

    Ainsi, ils permettront de s'assurer que le contenu de chaque fichier d'en-tête inclut (de manière directe ou indirecte) dans une unité de compilation soit strictement unique.

    Pour reprendre mon exemple précédant, mettons que le fichier d.hpp inclue lui aussi le fichier c.hpp (qui est inclut de manière indirecte avec le fichier a.hpp), les gardes anti inclusion multiple permettront d'éviter que le contenu de c.hpp se retrouve deux fois dans l'unité de compilation générée à partir de fichier.cpp (une fois à cause de l'inclusion de a.hpp et une autre à cause de l'inclusion de d.hpp).

    Par contre, si j'ai une deuxième unité de compilation générée à partir de autrefichier.cpp qui inclut lui aussi a.hpp et d.hpp, on retrouvera également forcément le contenu de a.hpp, de b.hpp, de c.hpp et de d.hpp (une seule et unique fois) dans cette deuxième unité de compilation.

    Et ca, ca commence à ressembler à la situation dans laquelle tu te trouve, car le contenu de ton fichier d'en-tête def_constante.h se retrouve aussi bien dans l'unité de compilation générée à partir de mainwindow.cpp que dans celle générée à partir de stuff.cpp.

    Seulement, un fichier d'en-tête contient une instruction qui incite le compilateur à générer du code binaire exécutable et qu'il se retrouve dans plusieurs unités de compilation, hé bien, tu comprends assez facilement que ce code binaire exécutable va se retrouver ... dans les deux unités de compilation dans lequel ton fichier d'en-tête a été inclus.

    Et c'est là que les problèmes vont commencer à arriver, car, une fois que l'on a généré l'ensemble des fichiers objets correspondant aux différentes unités de compilation, ben, il va falloir les regrouper en un fichier exécutable "final" (le fichier .exe sous windows).

    Ce travail va être délégué à ce que l'on appelle "l'éditeur de liens" (ou linker en anglais), qui est soumis à une règle simple (appelée ODR pour One Definition Rule) qui veut que chaque symbole ne soit présent qu'une seule et unique fois dans l'exécutable concerné.

    Si, "par malheur" l'éditeur rencontre deux fois la définition du même symbole (je parle bien de la définition, et non de l'accès à ce symbole), hé ben, l'éditeur de liens ne saura pas lequel choisir, si bien que sa seule solution sera de lancer l'erreur que tu obtiens avant d'abandonner le travail.

    Or, le fait est que tu as -- justement -- dans ton fichier d'en-tête, deux lignes de code qui vont inciter le compilateur à générer du code binaire exécutable, à savoir:
    • QString ip_centrale("192.168.0.100"); et
    • QString tag_t1 = "<Temperature1>";

    parce que chacune de ces lignes signifie littéralement
    Je veux disposer d'une donnée appelée ip_centrale (respectivement tag_t1) qui soit de type QString et dont la valeur est "192.168.0.100" (respectivement "Temperature1"
    Et que, ben, il n'y a pas de miracle: pour que tu puisse disposer de ces données, il faudra bien qu'elles se trouvent "quelque part" dans la mémoire du programme.

    Mais, comme le compilateur n'a aucune idée de "ce qui se passe ailleurs" lorsqu'il traite une unité de compilation particulière (comme celle générée à partir de mainwindow.cpp ou de tuff.cpp), ben, qu'est ce qu'il va faire, à ton avis Je te le donne en mille: il va générer le code binaire exécutable qui consiste à placer ces variables en mémoire et ce... dans les deux unités de compilation.

    Et du coup, lorsque l'éditeur de liens va regrouper les deux fichiers objets pour générer l'exécutable, il va constater qu'il y a deux variables qui portent le même nom mais qui se trouvent (normalement) à deux adresses mémoire différentes. Et ca, il ne va pas accepter :-$

    Maintenant que l'on a une bonne idée du problème auquel tu es confronté et de la raison pour laquelle il se produit, on peut essayer d'y remédier. Comment, me demanderas-tu.

    Hé bien, on a deux solutions:
    • Soit on s'arrange pour que ces deux variables ne soient réellement créer que dans une seule unité de compilation
    • soit on fait en sorte que, si ces variables sont créées plusieurs fois, ce soit à chaque fois dans un contexte différent pour évite les conflits.


    Pour s'assurer que les variables ne sont créées que dans une seule unité de compilation

    En fait, il est "assez facile" de s'assurer qu'une variable globale ne sera créée que dans une seule et unique unité de compilation.

    Tout ce que l'on a à faire, c'est de déclarer cette variable au niveau du fichier d'en-tête (*.h / *.hpp) comme étant extern et de la définir effectivement au niveau d'un (et un seul) fichier d'implémentation (*.cpp).

    Cela donnerait quelque chose comme
    dans def_constant.h
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    extern QString ip_central; // déclare une variable nommée ip_central
    extern QString tag_t1;  // déclare une variable nommée tag_t1
    Le mot clé extern va en effet dire au compilateur quelque chose comme
    je te garanti qu'il existe "quelque part", dans le programme une variable nommée (ip_central) et tu peux donc considérer qu'elle existe n'importe où
    Et, bien sur, il faut alors respecter l'engagement que l'on a pris en s'assurant que cette variable sera bel et bien définie dans une (et une seule) unité de compilation, par exemple:
    dans stuff.cpp (et uniquement dans ce fichier .cpp)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
        QString ip_centrale("192.168.0.100"); // défini la variable nommée ip_central
        QString tag_t1 = "<Temperature1>";  // défini la variable nommée tag_t1

    créer des variables dans des contextes différents


    Une autre solution consiste à se dire que, la base du comportement d'une application écrite en C++ est la fonction, et de se rappeler que chaque fonction représente un contexte différent, et surtout que les variables déclarées à l'intérieur d'une fonction sont propres au contexte de la fonction en question.

    Ainsi, si j'ai un code qui ressemble à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    void foo(){
        QString machin;
        /* ... */
    }
    void bar(){
        QString machin;
        /* ... */
    }
    je me retrouve avec deux variables nommées machin qui sont de type QString totalement différentes, car il y en a une qui n'existe que dans le contexte de la fonction foo() et l'autre qui n'existe que dans le contexte de la fonction bar().

    De la même manière, je peux créer des types de donnée "complexes" qui formeront -- eux aussi -- des contextes bien spécifiques.

    Par exemple, si j'ai un code qui ressemble à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    struct Point3D{ // ca pourrait être une classe ;)
        int x;
        int y;
        int z;
    };
    les variables x, y et z ne pourront exister que ... lorsque je créerai effectivement une donnée de type Point3D, mais, mieux encore: si je crée plusieurs données de type Point3D (dans une fonction ou dans un autre type "plus complexe"), chaque donnée de type Point3D disposera de ... ses propres données x,y et z. Par exemple:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /* un triangle va utiliser troiis points pour définir ses cotés */
    struct Triangle{
        Point3D top; // contient des données x, y et z qui lui sont propres
        Point3D left;  // contient des données x, y et z qui lui sont propres
        Point3D right;  // contient des données x, y et z qui lui sont propres
    };
     
    /* OU OU OU */
    void truc(){
        Point3D start;  // contient des données x, y et z qui lui sont propres
        Point3D finish;  // contient des données x, y et z qui lui sont propres
        /* ... */
    }
    De plus, il est possible depuis C++11 de fournir une valeur par défaut au données membres d'une structure (ou d'une classe), si bien que l'on pourrait tout à fait décider de créer une structure nommée (essayons d'avoir un nom intelligent) Configuration et qui ressemblerait à quelque chose comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    struct Configuration{
        QString ip_centrale{"192.168.0.100"};
        QString tag_t1{"<Temperature1>"};
    };
    De cette manière, la valeur associée aux données membres ip_centrale et tag_t1 dépend du contexte défini par le type Configuration, mais les données de type Configuration pourront dépendre du contexte fourni par ... les fonctions dans lesquelles elles seront déclarées. Par exemple, si on a un code qui ressemble à

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    /* BIS  REPETITA
    void foo(){
        Configuration configInfo;
        /* ... */
    }
    void bar(){
        Configuration configInfo;
        /* ... */
    }
    nous avons bel et bien deux variables totalement différentes qui sont nommées configInfo, et qui sont de type Configuration, ce qui fait qu'elles disposent toutes les deux des informations (de type QString) nommées ip_centrale et tag_t1 (et dont les valeurs par défaut sont respectivement 192.168.0.100 et <Temperature1>).

    Pfiouuu... Avoue que c'est cool, non

    Alors, bien sur, il y a d'autres solutions si la valeur de ces deux données se peut (ou ne doivt) jamais être modifiée. Mais bon, ca, c'est une solution qui fonctionne ... parmi d'autres
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  3. #3
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    Si ce sont des constantes, où est const ?
    Et dans ce cas, autant utiliser constexpr.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  4. #4
    Membre chevronné Avatar de Astraya
    Homme Profil pro
    Consommateur de café
    Inscrit en
    Mai 2007
    Messages
    1 043
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France

    Informations professionnelles :
    Activité : Consommateur de café
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 043
    Points : 2 234
    Points
    2 234
    Par défaut
    Je suis toujours étonné que jamais on ne parle dans ce cas des règles dites "Storage class specifier" (Désolé je ne sais vraiment pas comment traduire ca), même à l'école ou dans un milieu pro, je n'ai jamais entendu quelqu'un en parler pour justifier ça. Bien sur cela demande de comprendre comment se passe la compilation comme la fait Koala, ensuite il faut absolument maitriser les règles des storage class specifiers (https://en.cppreference.com/w/cpp/la...orage_duration) (section Linkage) . Et le reste devient tellement plus simple, tu n'auras plus qu'à penser : quelle règle de visibilité je veux appliquer à ma variable/fonction/objet
    Homer J. Simpson


  5. #5
    Membre éprouvé
    Femme Profil pro
    ..
    Inscrit en
    Décembre 2019
    Messages
    562
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 94
    Localisation : Autre

    Informations professionnelles :
    Activité : ..

    Informations forums :
    Inscription : Décembre 2019
    Messages : 562
    Points : 1 253
    Points
    1 253
    Par défaut
    Citation Envoyé par Astraya Voir le message
    Je suis toujours étonné que jamais on ne parle dans ce cas des règles dites "Storage class specifier" (Désolé je ne sais vraiment pas comment traduire ca),
    Salut,

    Ça se dit presque pareil. Au singulier un spécificateur de classe de stockage, au pluriel les spécificateurs de classes de stockage.

  6. #6
    Membre chevronné Avatar de Astraya
    Homme Profil pro
    Consommateur de café
    Inscrit en
    Mai 2007
    Messages
    1 043
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France

    Informations professionnelles :
    Activité : Consommateur de café
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 043
    Points : 2 234
    Points
    2 234
    Par défaut
    Bon bas voila merci
    Dans le lien cppreference, la partie importante est le Linkage (no linkage, internal, external et depuis c++20 le module linkages)
    Homer J. Simpson


  7. #7
    Membre habitué
    Profil pro
    Développeur informatique
    Inscrit en
    Février 2008
    Messages
    289
    Détails du profil
    Informations personnelles :
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Février 2008
    Messages : 289
    Points : 151
    Points
    151
    Par défaut
    Merci Koala01 pour cette longue explication.
    J'étais passé complètement à côté de ces lignes qui généraient du code.
    J'ai opté pour la solution de la structure et ça fonctionne très bien.

    Merci.

  8. #8
    Expert éminent
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 565
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 61
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 565
    Points : 7 648
    Points
    7 648
    Par défaut
    Bonjour,

    La solution de la structure n'est pas la plus simple. A moins que tu n'ai écrit un code comme:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     // j'ai besoin de l'adresse IP, je la demande à la structure qui par "magie" elle peut être écrite dans un entête visible de tous
        auto   ip_address = Configuration{}.ip_centrale;
    Et là, ça marche mais ça serait alors un hors-sujet à ne surtout pas faire!

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Générer un problème d'inclusions multiples
    Par rdtech dans le forum Linux
    Réponses: 1
    Dernier message: 27/09/2021, 19h42
  2. Problème d'inclusion "multiple" de headers
    Par NeMo_O dans le forum C
    Réponses: 12
    Dernier message: 21/05/2008, 21h25
  3. Problème d'inclusions multiples
    Par Le Furet dans le forum C
    Réponses: 2
    Dernier message: 03/10/2005, 23h59
  4. Problème d'inclusion
    Par degreste dans le forum MFC
    Réponses: 5
    Dernier message: 27/01/2004, 00h56
  5. Réponses: 6
    Dernier message: 25/03/2002, 21h11

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo