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 de Link


Sujet :

C++

  1. #21
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 395
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 395
    Par défaut
    Ce qu'il faut surtout comprendre avec ces erreurs, c'est la différence entre le fait qu'un header soit inclus plusieurs fois dans la même compilation, et le fait qu'il soit compilé plusieurs fois.
    • Le premier problème donnera des erreurs de compilation, typiquement des redéfinitions de macros et de types
    • Le second donnera des erreurs d'édition de liens, typiquement des définitions multiples de variables globales.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Ce qu'il faut comprendre, c'est que:
    1. un compilateur lit le code "de haut en bas" et ne connait donc, lorsqu'il arrive à une ligne donnée, que ce qu'il a déjà rencontré dans les lignes précédentes
    2. l'ordre de compilation des fichiers n'influe absolument pas... c'est l'édition des liens qui fera correspondre le tout
    3. Le compilateur "oublie" tous les symboles qu'il a pu définir pour la compilation d'un fichier dés qu'il a fini de traiter ce fichier

    Le (1) explique que le code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    int main()
    {
        foo();
        return 0;
    }
    void foo()
    {
    }
    ne compilera pas parce que, lorsque le compilateur rencontre l'invocation de foo dans la fonction principale, il ne sait pas encore que foo existe, et il ne peut pas vérifier, entre autres, si lui on passe les bons paramètres

    Par contre, le code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void foo() // définition servant aussi de déclaration
               // Le compilateur crée un identifiant unique
               // et le code machine et assigne l'identifiant au code
    {
    }
    int main()
    {
        foo();
        return 0;
    }
    ou
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    void foo(); // déclaration uniquement (indique seulement au compilateur
                // que la fonction existe... le compilateur crée un identifiant
                // unique pour cette fonction
    int main()
    {
        foo();
        return 0;
    }
    void foo() // définition (implémentation) uniquement
               // Le compilateur transforme le code (C++) en code machine
               // et assigne l'identifiant unique au code machine obtenu
    {
    }
    compilera parce que, lorsque le compilateur rencontre l'invocation de foo dans la fonction principale, il a déjà rencontré la déclaration de foo (même si c'est la définition/implémentation dans le premier)

    Il faut bien comprendre ici qu'un même nom, une même signature, dans un même espace de noms produira toujours... le même identifiant

    Le (2) et le (3) nous font prendre conscience qu'il faut disposer d'un moyen "simple et efficace" pour nous assurer que le compilateur connaisse les différents symboles auxquels il devra recourir pour compiler un nombre indéfini de fichiers...

    En effet, nous pourrions envisager d'écire des fichiers sous la forme de
    fichier1.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    /* les fonctions nécessaires, mais définies dans fichier2.cpp et fichier3.cpp*/
    void bar();
    void foobar();
    /* la fonction définie dans fichier1.cpp*/
    void foo()
    {
       bar();
       foobar();
    }
    fichier2.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    /* les fonctions nécessaires, mais définies dans fichier1.cpp et fichier3.cpp*/
    void foo();
    void foobar();
    /* la fonction définie dans fichier1.cpp*/
    void bar()
    {
       foo();
       foobar();
    }
    fichier3.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    /* les fonctions nécessaires, mais définies dans fichier1.cpp et fichier2.cpp*/
    void foo();
    void bar();
    /* la fonction définie dans fichier1.cpp*/
    void foobar()
    {
       foo();
       bar();
    }
    Si ce n'est une récursivité "malsaine" introduite par l'exemple lui-même, nous pourrions parfaitement avoir trois fichiers prenant cette forme, et il y a même de fortes chances pour que ca compile sans problème

    Il "suffirait" de compiler chaque fichier séparément, et d'effectuer l'édition de liens de manière correcte pour avoir quelque chose de tout à fait juste (ou peu s'en faut)

    Nous pourrions donc nous en tenir, pour l'instant, à deux étapes majeurs:
    • la transformation du code source en langage machine (création de fichier objet) et
    • la création des liaisons entre les différents fichiers objet en vue de créer l'exécutable

    Mais vous admettrez qu'il devient rapidement difficile de gérer la déclaration de chaque fonction / structure dans l'ensemble des fichiers qui en ont besoin...

    C'est la raison pour laquelle nous avons la possibilité de séparer la déclaration des fonction (et du contenu d'une structure) de l'implémentation.

    Cette possibilité nous est donnée par... les fichiers d'en-tête.

    Mais cela implique qu'il faille ajouter une étape au travail de création de l'exécutable: l'étape du passage du préprocesseur

    En effet, lorsque l'on écrit des lignes commençant par
    • #include
    • #ifndef
    • #define
    • #else
    • #else if
    • #if defined
    • #end
    • ...j'en oublie sans doute
    nous donnons des instructions au... préprocesseur (qui va travailler avant même que le compilateur ne commence à transformer le code source en code machine)

    La directive #include va avoir pour résultat de copier le contenu du fichier inclus à la place de la directive elle-même.

    trois fichier ressemblant à
    fichier1.hpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    #ifndef FICHIER1_HPP
    #define FICHIER1_HPP
    class MyClasse
    {
       son contenu
    };
    #endif //FICHIER1_HPP
    fichier2.hpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    #ifndef FICHIER2_HPP
    #define FICHIER2_HPP
    #include "fichier1.hpp"
    /* contenu de fichier2.hpp */
    #endif //FICHIER2_HPP
    truc.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    #include "fichier1.hpp"
    #include "fichier2.hpp"
    /* contenu de truc.cpp */
    nous aurions, après la seule étape de gestion des inclusions, des fichier correspondant à
    fichier2.hpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #ifndef FICHIER2_HPP
    #define FICHIER2_HPP
    #ifndef FICHIER1_HPP
    #define FICHIER1_HPP
    class MyClasse
    {
       son contenu
    };
    #endif //FICHIER1_HPP
    /* contenu de fichier2.hpp */
    #endif //FICHIER2_HPP
    et à
    truc.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     
    #ifndef FICHIER1_HPP   [1]
    #define FICHIER1_HPP  [2]
    class MyClasse
    {
       son contenu
    };
    #endif //FICHIER1_HPP  [3]
    #ifndef FICHIER2_HPP  [4]
    #define FICHIER2_HPP  [5]
    #ifndef FICHIER1_HPP   [6]
    #define FICHIER1_HPP   [7]
    class MyClasse
    {
       son contenu
    };
    #endif //FICHIER1_HPP  [8]
    /* contenu de fichier2.hpp */
    #endif //FICHIER2_HPP [9]
    /* contenu de truc.cpp */
    S'il n'y avait pas les gardes contre l'inclusion multiple ni (surtout) la gestion des directives de compilation conditionnelle, le contenu de fichier1.hpp apparaitrait deux fois et donc, la classe MaClasse a ne respecterait pas la règle de... la définition unique

    C'est pourquoi, après l'étape d'inclusion, il y a une étape gestion des directives de compilation conditionnelle, et cela va fonctionner ainsi (sur le fichier truc.cpp)
    • en [1], FICHIER1_HPP n'est pas connu du préprocesseur, il garde donc tout ce qu'il y a entre [1] et [3], c'est à dire que
    • en [2] le préprocesseur crée le symbole FICHIER1_HPP
    • Le compilateur dispose de la définition de MaClass
    • [3]n'est que la fin de la structure de contrôle (ouverte en [1])
    • en [4] le préprocesseur ne connait pas encore le symbole FICHIER2_HPP, il garde donc tout ce qu'il y a entre [4] et [9]
    • en [5] Le préprocesseur crée le symbole FICHIER2_HPP
    • en [6] le préprocesseur connait le symbole FICHIER1_HPP, et il supprime donc tout ce qu'il y a entre [6] et [8]
    • [7]est supprimé (cf ci-dessus)
    • la deuxième définition de MaClasse est supprimée (cf ci-dessus)
    • [8]est supprimé (cf ci-dessus)
    • [9]est la fin de la structure de controle (ouverte en [4])

    Après cette étape nous avons donc, pour truc.cpp, un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #ifndef FICHIER1_HPP   [1]
    #define FICHIER1_HPP  [2]
    class MyClasse
    {
       son contenu
    };
    #endif //FICHIER1_HPP  [3]
    #ifndef FICHIER2_HPP  [4]
    #define FICHIER2_HPP  [5]
    /* contenu de fichier2.hpp */
    #endif //FICHIER2_HPP [9]
    /* contenu de truc.cpp */
    voire, après un peu de nettoyage (mais je n'en suis pas sur du tout)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    class MyClasse
    {
       son contenu
    };
    /* contenu de fichier2.hpp */
    /* contenu de truc.cpp */
    Et le but recherché est atteint: tout ce qui est déclaré dans les différents fichiers d'en-tête est déclaré avant que le compilateur n'ait besoin d'y accéder et le principe du "one definition rule" est respecté

    Le compilateur peut donc créer à son aise les fichier objet (qui contiennent le "code machine") et l'éditeur de lien sait donc faire correspondre les invocations des différents symbole entre les fichiers...

    Il faut enfin comprendre qu'un fichier d'en-tête n'a absolument pas pour objectif d'être compilé, et que si l'on parle (de manière un peu maladroite) de définition de structure ou de classe, cette définition correspond en réalité à... la déclaration du contenu de la structure (ou de la classe), mais qu'il n'y a absolument pas de réservation d'espace mémoire lorsque cette définition de classe (ou de structure) survient

    Enfin, je prévois l'objection du
    Oui, mais, quand on déclare une fonction inline, on doit mettre la définition(implémentation) de cette fonction dans le fichier d'en-tête
    ou du
    Oui, mais quand on déclare une fonction template ou des fonctions dans une classe (ou une structure) template, on doit mettre la définition(implémentation) de cette fonction (ou méthode) dans le fichier d'en-tête
    Et je vous répond donc tout de suite que c'est normal...

    Lorsque nous utilisons une fonction inline, nous demandons au compilateur de remplacer l'appel de la fonction par... le code correspondant à cette fonction.

    Il doit donc, lorsque nous invoquons cette fonction... disposer du code approprié ...

    Mais, la création du code machine correspondant se fait malgré tout toujours... dans le fichier d'implémentation dans lequel l'invocation a lieu

    De la même manière, pour les fonction template ou les méthodes de classes (ou de structures) template, le compilateur n'est en mesure de créer le code machine correspondant qu'une fois... qu'il sait quel type utiliser, et donc, au moment de l'invocation de la fonction (ou de la méthode).

    Autrement dit: il ne peut créer le code machine correspondant à la fonction template que... dans le fichier d'implémentation dans lequel se trouve l'invocation de cette fonction

    NOTA: ici encore, j'ai pris quelques raccourcis pour permettre la compréhension du principe... quelques termes sont peut etre mal choisi, quelques détails peuvent être imprécis ou, à la limite, pas tout à fait juste, mais bon... c'est comme tous les romans du genre: s'il fallait une précision complete, je pourrais en faire un livre
    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. #23
    Membre très actif
    Avatar de buggen25
    Ingénieur développement logiciels
    Inscrit en
    Août 2008
    Messages
    554
    Détails du profil
    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Communication - Médias

    Informations forums :
    Inscription : Août 2008
    Messages : 554
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Ce qu'il faut comprendre, c'est que:
    1. un compilateur lit le code "de haut en bas" et ne connait donc, lorsqu'il arrive à une ligne donnée, que ce qu'il a déjà rencontré dans les lignes précédentes
    2. l'ordre de compilation des fichiers n'influe absolument pas... c'est l'édition des liens qui fera correspondre le tout
    3. Le compilateur "oublie" tous les symboles qu'il a pu définir pour la compilation d'un fichier dés qu'il a fini de traiter ce fichier

    Le (1) explique que le code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    int main()
    {
        foo();
        return 0;
    }
    void foo()
    {
    }
    ne compilera pas parce que, lorsque le compilateur rencontre l'invocation de foo dans la fonction principale, il ne sait pas encore que foo existe, et il ne peut pas vérifier, entre autres, si lui on passe les bons paramètres

    Par contre, le code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void foo() // définition servant aussi de déclaration
               // Le compilateur crée un identifiant unique
               // et le code machine et assigne l'identifiant au code
    {
    }
    int main()
    {
        foo();
        return 0;
    }
    ou
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    void foo(); // déclaration uniquement (indique seulement au compilateur
                // que la fonction existe... le compilateur crée un identifiant
                // unique pour cette fonction
    int main()
    {
        foo();
        return 0;
    }
    void foo() // définition (implémentation) uniquement
               // Le compilateur transforme le code (C++) en code machine
               // et assigne l'identifiant unique au code machine obtenu
    {
    }
    compilera parce que, lorsque le compilateur rencontre l'invocation de foo dans la fonction principale, il a déjà rencontré la déclaration de foo (même si c'est la définition/implémentation dans le premier)

    Il faut bien comprendre ici qu'un même nom, une même signature, dans un même espace de noms produira toujours... le même identifiant

    Le (2) et le (3) nous font prendre conscience qu'il faut disposer d'un moyen "simple et efficace" pour nous assurer que le compilateur connaisse les différents symboles auxquels il devra recourir pour compiler un nombre indéfini de fichiers...

    En effet, nous pourrions envisager d'écire des fichiers sous la forme de
    fichier1.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    /* les fonctions nécessaires, mais définies dans fichier2.cpp et fichier3.cpp*/
    void bar();
    void foobar();
    /* la fonction définie dans fichier1.cpp*/
    void foo()
    {
       bar();
       foobar();
    }
    fichier2.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    /* les fonctions nécessaires, mais définies dans fichier1.cpp et fichier3.cpp*/
    void foo();
    void foobar();
    /* la fonction définie dans fichier1.cpp*/
    void bar()
    {
       foo();
       foobar();
    }
    fichier3.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    /* les fonctions nécessaires, mais définies dans fichier1.cpp et fichier2.cpp*/
    void foo();
    void bar();
    /* la fonction définie dans fichier1.cpp*/
    void foobar()
    {
       foo();
       bar();
    }
    Si ce n'est une récursivité "malsaine" introduite par l'exemple lui-même, nous pourrions parfaitement avoir trois fichiers prenant cette forme, et il y a même de fortes chances pour que ca compile sans problème

    Il "suffirait" de compiler chaque fichier séparément, et d'effectuer l'édition de liens de manière correcte pour avoir quelque chose de tout à fait juste (ou peu s'en faut)

    Nous pourrions donc nous en tenir, pour l'instant, à deux étapes majeurs:
    • la transformation du code source en langage machine (création de fichier objet) et
    • la création des liaisons entre les différents fichiers objet en vue de créer l'exécutable

    Mais vous admettrez qu'il devient rapidement difficile de gérer la déclaration de chaque fonction / structure dans l'ensemble des fichiers qui en ont besoin...

    C'est la raison pour laquelle nous avons la possibilité de séparer la déclaration des fonction (et du contenu d'une structure) de l'implémentation.

    Cette possibilité nous est donnée par... les fichiers d'en-tête.

    Mais cela implique qu'il faille ajouter une étape au travail de création de l'exécutable: l'étape du passage du préprocesseur

    En effet, lorsque l'on écrit des lignes commençant par
    • #include
    • #ifndef
    • #define
    • #else
    • #else if
    • #if defined
    • #end
    • ...j'en oublie sans doute
    nous donnons des instructions au... préprocesseur (qui va travailler avant même que le compilateur ne commence à transformer le code source en code machine)

    La directive #include va avoir pour résultat de copier le contenu du fichier inclus à la place de la directive elle-même.

    trois fichier ressemblant à
    fichier1.hpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    #ifndef FICHIER1_HPP
    #define FICHIER1_HPP
    class MyClasse
    {
       son contenu
    };
    #endif //FICHIER1_HPP
    fichier2.hpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    #ifndef FICHIER2_HPP
    #define FICHIER2_HPP
    #include "fichier1.hpp"
    /* contenu de fichier2.hpp */
    #endif //FICHIER2_HPP
    truc.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    #include "fichier1.hpp"
    #include "fichier2.hpp"
    /* contenu de truc.cpp */
    nous aurions, après la seule étape de gestion des inclusions, des fichier correspondant à
    fichier2.hpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #ifndef FICHIER2_HPP
    #define FICHIER2_HPP
    #ifndef FICHIER1_HPP
    #define FICHIER1_HPP
    class MyClasse
    {
       son contenu
    };
    #endif //FICHIER1_HPP
    /* contenu de fichier2.hpp */
    #endif //FICHIER2_HPP
    et à
    truc.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     
    #ifndef FICHIER1_HPP   [1]
    #define FICHIER1_HPP  [2]
    class MyClasse
    {
       son contenu
    };
    #endif //FICHIER1_HPP  [3]
    #ifndef FICHIER2_HPP  [4]
    #define FICHIER2_HPP  [5]
    #ifndef FICHIER1_HPP   [6]
    #define FICHIER1_HPP   [7]
    class MyClasse
    {
       son contenu
    };
    #endif //FICHIER1_HPP  [8]
    /* contenu de fichier2.hpp */
    #endif //FICHIER2_HPP [9]
    /* contenu de truc.cpp */
    S'il n'y avait pas les gardes contre l'inclusion multiple ni (surtout) la gestion des directives de compilation conditionnelle, le contenu de fichier1.hpp apparaitrait deux fois et donc, la classe MaClasse a ne respecterait pas la règle de... la définition unique

    C'est pourquoi, après l'étape d'inclusion, il y a une étape gestion des directives de compilation conditionnelle, et cela va fonctionner ainsi (sur le fichier truc.cpp)
    • en [1], FICHIER1_HPP n'est pas connu du préprocesseur, il garde donc tout ce qu'il y a entre [1] et [3], c'est à dire que
    • en [2] le préprocesseur crée le symbole FICHIER1_HPP
    • Le compilateur dispose de la définition de MaClass
    • [3]n'est que la fin de la structure de contrôle (ouverte en [1])
    • en [4] le préprocesseur ne connait pas encore le symbole FICHIER2_HPP, il garde donc tout ce qu'il y a entre [4] et [9]
    • en [5] Le préprocesseur crée le symbole FICHIER2_HPP
    • en [6] le préprocesseur connait le symbole FICHIER1_HPP, et il supprime donc tout ce qu'il y a entre [6] et [8]
    • [7]est supprimé (cf ci-dessus)
    • la deuxième définition de MaClasse est supprimée (cf ci-dessus)
    • [8]est supprimé (cf ci-dessus)
    • [9]est la fin de la structure de controle (ouverte en [4])

    Après cette étape nous avons donc, pour truc.cpp, un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #ifndef FICHIER1_HPP   [1]
    #define FICHIER1_HPP  [2]
    class MyClasse
    {
       son contenu
    };
    #endif //FICHIER1_HPP  [3]
    #ifndef FICHIER2_HPP  [4]
    #define FICHIER2_HPP  [5]
    /* contenu de fichier2.hpp */
    #endif //FICHIER2_HPP [9]
    /* contenu de truc.cpp */
    voire, après un peu de nettoyage (mais je n'en suis pas sur du tout)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    class MyClasse
    {
       son contenu
    };
    /* contenu de fichier2.hpp */
    /* contenu de truc.cpp */
    Et le but recherché est atteint: tout ce qui est déclaré dans les différents fichiers d'en-tête est déclaré avant que le compilateur n'ait besoin d'y accéder et le principe du "one definition rule" est respecté

    Le compilateur peut donc créer à son aise les fichier objet (qui contiennent le "code machine") et l'éditeur de lien sait donc faire correspondre les invocations des différents symbole entre les fichiers...

    Il faut enfin comprendre qu'un fichier d'en-tête n'a absolument pas pour objectif d'être compilé, et que si l'on parle (de manière un peu maladroite) de définition de structure ou de classe, cette définition correspond en réalité à... la déclaration du contenu de la structure (ou de la classe), mais qu'il n'y a absolument pas de réservation d'espace mémoire lorsque cette définition de classe (ou de structure) survient

    Enfin, je prévois l'objection du

    ou du

    Et je vous répond donc tout de suite que c'est normal...

    Lorsque nous utilisons une fonction inline, nous demandons au compilateur de remplacer l'appel de la fonction par... le code correspondant à cette fonction.

    Il doit donc, lorsque nous invoquons cette fonction... disposer du code approprié ...

    Mais, la création du code machine correspondant se fait malgré tout toujours... dans le fichier d'implémentation dans lequel l'invocation a lieu

    De la même manière, pour les fonction template ou les méthodes de classes (ou de structures) template, le compilateur n'est en mesure de créer le code machine correspondant qu'une fois... qu'il sait quel type utiliser, et donc, au moment de l'invocation de la fonction (ou de la méthode).

    Autrement dit: il ne peut créer le code machine correspondant à la fonction template que... dans le fichier d'implémentation dans lequel se trouve l'invocation de cette fonction

    NOTA: ici encore, j'ai pris quelques raccourcis pour permettre la compréhension du principe... quelques termes sont peut etre mal choisi, quelques détails peuvent être imprécis ou, à la limite, pas tout à fait juste, mais bon... c'est comme tous les romans du genre: s'il fallait une précision complete, je pourrais en faire un livre
    Et ça donne quoi en français

  4. #24
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par buggen25 Voir le message
    Et ça donne quoi en français
    La possibilité de comprendre en gros comment fonctionne le compilateur et le pourquoi du comment des différents conseils que l'on peut donner sur le forum en ce qui concerne la compilation

    Plus précisément, donner l'occasion de comprendre qu'un fichier d'en-tête n'a pas pour objectif d'être compilé, et de comprendre la manière dont les gardes contre l'inclusion multiples fonctionneront
    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

  5. #25
    Membre très actif
    Avatar de buggen25
    Ingénieur développement logiciels
    Inscrit en
    Août 2008
    Messages
    554
    Détails du profil
    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Communication - Médias

    Informations forums :
    Inscription : Août 2008
    Messages : 554
    Par défaut
    Citation Envoyé par koala01 Voir le message
    La possibilité de comprendre en gros comment fonctionne le compilateur et le pourquoi du comment des différents conseils que l'on peut donner sur le forum en ce qui concerne la compilation

    Plus précisément, donner l'occasion de comprendre qu'un fichier d'en-tête n'a pas pour objectif d'être compilé, et de comprendre la manière dont les gardes contre l'inclusion multiples fonctionneront
    Je pense que j'ai déja etudié ça. c'est un truc d'analyse lexical, annalyseur syntaxique. Trop compliqué mais je devrais m'y remettre...

  6. #26
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Par défaut
    Citation Envoyé par buggen25 Voir le message
    c'est un truc d'analyse lexical, annalyseur syntaxique. Trop compliqué mais je devrais m'y remettre...
    Non, c'est plus de l'ordre du pre-processing. Ca intervient avant la compilation au sens génération de code. En gros, il faut plus le comprendre comme tout un mécanisme qui retripatouille tes sources avant de les données à manger au compilateur à proprement parlé.
    Analyse lexical, syntaxique, sémantique relève plus de la traduction (à prendre avec des guillemets) du code langage haut niveau vers langage exécutable.

  7. #27
    Membre très actif
    Avatar de buggen25
    Ingénieur développement logiciels
    Inscrit en
    Août 2008
    Messages
    554
    Détails du profil
    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Communication - Médias

    Informations forums :
    Inscription : Août 2008
    Messages : 554
    Par défaut
    Citation Envoyé par 3DArchi Voir le message
    Non, c'est plus de l'ordre du pre-processing. Ca intervient avant la compilation au sens génération de code. En gros, il faut plus le comprendre comme tout un mécanisme qui retripatouille tes sources avant de les données à manger au compilateur à proprement parlé.
    Analyse lexical, syntaxique, sémantique relève plus de la traduction (à prendre avec des guillemets) du code langage haut niveau vers langage exécutable.
    ça releve donc de l'ordre du preprocesseur. ça appartient donc a la meme famille que les macros.

  8. #28
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Je me permet de revenir un peu en arrière sur deux interventions...
    Citation Envoyé par deubelte Voir le message
    Les commentaires sont aussi lus par le compilateur??
    Normalement, non...

    Sauf erreur, l'une des premières taches du préprocesseur, avant même de gérer les changements dus aux directives include et define, c'est de supprimer les commentaires...
    Citation Envoyé par 3DArchi Voir le message
    C'est vrai de gcc, mais pas de visual
    Et pourtant, sauf erreur toujours, il me semble effectivement que la norme précise que tout fichier doit terminer par une ligne vide (si ce n'est pas dans la norme du C++, ce peut être dans celle propre au C, et apparaitre alors dans l'appendice dédiée à la compatibilité )

    Encore une fois, le fait que VC++ ne soit pas attentif à ce point est - si je ne suis pas dans l'erreur - une des (trop nombreuses) libertés prises par la firme de redmond vis à vis de la norme
    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

  9. #29
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 395
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 395
    Par défaut
    Euh... Si c'était une règle fixée par la norme, gcc donnerait une erreur plutôt qu'un warning, non?
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  10. #30
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par Médinoc Voir le message
    Euh... Si c'était une règle fixée par la norme, gcc donnerait une erreur plutôt qu'un warning, non?
    Pas forcément...

    Il y a de nombreuses règles qui, n'étant pas forcément fatales, se transforment en avertissement

    Un simple exemple:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    class A
    {
        int i;
        int j;
        public:
            A(int i, int j):j(j),i(i){}
    };
    La norme indique que les membre d'une structure sont initialisés dans l'ordre d'apparition dans le code (i est normalement initialisé avant j), et ce code se contente d'un warning sous la forme de
    ||=== forumcpp, Debug ===|
    main.cpp||In constructor 'A::A(int, int)'
    main.cpp|6|warning: 'A::j' will be initialized after|
    main.cpp|5|warning: 'int A::i'|
    main.cpp|8|warning: when initialized here|
    ||=== Build finished: 0 errors, 3 warnings ===|
    (mais peut-être s'agit-il d'une liberté prise par Gcc... il faudrait vérifier )

    Et, concernant le "no new line at end of file", on trouve dans la section 2.1 (Phases of translation)
    2 Each instance of a new-line character and an immediately preceding backslash character is deleted, splicing physical source lines to form logical source lines. If, as a result, a character sequence that matches the syntax of a universal-character-name is produced, the behavior is undefined. If a source file that is not empty does not end in a new-line character, or ends in a new-line character immediately preceded by a backslash character, the behavior is undefined.
    et, dans la section 16 Preprocessing directives, on lit:
    A preprocessing directive consists of a sequence of preprocessing tokens. The first token in the sequence is a # preprocessing token that is either the first character in the source file (optionally after white space containing no new-line characters) or that follows white space containing at least one new-line character. The last token in the sequence is the first new-line character that follows the first token in the sequence.
    et la grammaire indique
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    preprocessing-file:
        groupopt
    group:
        group-part
        group group-part
    group-part:
        pp-tokensopt new-line
    if-section
        control-line
    if-section:
         if-group elif-groupsopt else-groupopt endif-line
    if-group:
        # if constant-expression new-line groupopt
        # ifdef identifier new-line groupopt
        # ifndef identifier new-line groupopt
    elif-groups:
        elif-group
        elif-groups elif-group
    elif-group:
        # elif constant-expression new-line groupopt
    else-group:
        # else new-line groupopt
    endif-line:
        # endif new-line
    control-line:
        # include pp-tokens new-line
        # define identifier replacement-list new-line
        # define identifier lparen identifier-listopt ) replacement-list new-line
        # undef identifier new-line
        # line pp-tokens new-line
        # error pp-tokensopt new-line
        # pragma pp-tokensopt new-line
        # new-line
    lparen:
        the left-parenthesis character without preceding white-space
    replacement-list:
        pp-tokensopt
    pp-tokens:
        preprocessing-token
        pp-tokens preprocessing-token
    new-line:
        the new-line character
    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

  11. #31
    Membre très actif
    Avatar de buggen25
    Ingénieur développement logiciels
    Inscrit en
    Août 2008
    Messages
    554
    Détails du profil
    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Communication - Médias

    Informations forums :
    Inscription : Août 2008
    Messages : 554
    Par défaut
    Et c'est quoi comme grammaire , une CFG (Contexte Free Grammar) une grammaire a contexte libre, L'HQMOC++ ?

  12. #32
    Membre Expert
    Homme Profil pro
    edi
    Inscrit en
    Juin 2007
    Messages
    941
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Gironde (Aquitaine)

    Informations professionnelles :
    Activité : edi

    Informations forums :
    Inscription : Juin 2007
    Messages : 941
    Par défaut
    @koala01 (pour son magnifique pavé sur le travail du pré-processeur et du compilateur - mais comment il peut en écrire autant ?! )
    Simple curiosité :
    Si j'ai un fichier MaClasse.h, MaClasse.cpp et main.cpp, MaClasse sera défini par le compilateur dans MaClasse.o et main.o. Comment l'éditeur de lien s'y retrouve-t-il ? Il voit que les définitions de MaClasse dans MaClasse.o et main.o sont identiques et considère qu'il s'agit donc de la seule et même classe ?

  13. #33
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par Noxen Voir le message
    @koala1 (pour son magnifique pavé sur le travail du pré-processeur et du compilateur - mais comment il peut en écrire autant ?! )
    En réfléchissant et en appliquant ce que suggère ma signature
    Citation Envoyé par Noxen Voir le message
    Simple curiosité :
    Si j'ai un fichier MaClasse.h, MaClasse.cpp et main.cpp, MaClasse sera défini par le compilateur dans MaClasse.o et main.o. Comment l'éditeur de lien s'y retrouve-t-il ? Il voit que les définitions de MaClasse dans MaClasse.o et main.o sont identiques et considère qu'il s'agit donc de la seule et même classe ?
    Parce que la définition d'une classe est en réalité surtout... la déclaration de son contenu...

    MaClasse est donc *connue* par le compilateur lorsqu'il travaille sur main.cpp (il sait que les fonctions et les variables membres de MaClasse existent) et s'en contente, alors qu'elles sont implémentées uniquement dans MaClasse.cpp (c'est lorsque le compilateur traduit MaClasse.cpp et uniquement à ce moment là qu'il crée le code machine correspondant)

    Du coup, dans MaClasse.o, tu trouve le code machine correspondant aux fonctions implémentées (définies) dans MaClasse.cpp et, dans main.o tu trouve le code machine correspondant aux fonctions implémentées dans... main.cpp.

    C'est ensuite à l'éditeur de liens de faire son travail en remplaçant, dans main.o les symboles correspondant aux fonctions implémentées dans MaClasse.o par les adresses en mémoire correspondantes
    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

  14. #34
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Par défaut
    En gros, le compilateur fait :
    -> Dans MaClasse.cpp: il convertit le code de MaClasse::MaMethode() en code exécutable
    -> Dans main.cpp, quand il rencontre MonObjet.MaMethode(), il utilise le MaClasse.h pour 1/faire des vérifications au niveau du 'langage' (vérifier que MaMethode existe, que les bons paramètres sont fournis, que c'est 'const-correct', etc...) et 2/générer le code : les paramètres à passer, la façon de les passer, l'appel à MaMethode (encore sous forme de symbole) et la façon de récupérer le résultat.

    L'éditeur de lien va en gros assembler les différents .o en un exécutable/dll et 'résoudre' les symboles: dans main.o il va remplacer le symbole MaMethode par l'appel effectif du code qui se trouve dans MaClasse.o

    [EDIT]: si on en revient au problème de départ: 1 variable définie dans .h et inclue dans 2 .cpp : à chaque fois le compilo à générer 2 variables avec le même symbole. Quand l'éditeur de lien arrive, il trouve dans 1.o et 2.o le même symbole et il râle !

  15. #35
    Membre Expert
    Homme Profil pro
    edi
    Inscrit en
    Juin 2007
    Messages
    941
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Gironde (Aquitaine)

    Informations professionnelles :
    Activité : edi

    Informations forums :
    Inscription : Juin 2007
    Messages : 941
    Par défaut
    En fait ce qui m'interroge porte surtout sur la capacité de l'éditeur de lien de reconnaître une même classe dans des fichiers objets différents.
    Au fond, les .h c'est (entre autres) une astuce pour éviter de définir une classe à la mano dans MaClasse.cpp et main.cpp. Mais si on définit MaClasse da façon différente dans les 2 fichiers, ça n'ira pas (à priori, mais Visual C++ semble vouloir me donner tort ; mais bon, Visual C++ et la norme ... ).
    Sur quoi l'éditeur de lien se fonde-t-il pour voir qu'il s'agit de la même classe dans les 2 fichiers objets ? L'identité du code objet correpondant à la classe ? des messages supplémentaires ? (merci en tout cas pour toutes vos infos.)

  16. #36
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Par défaut
    Le code se décompose en deux parties : la déclaration et la définition. La déclaration ne fait que dire au compilateur quels sont les symboles qui existent et à quoi ils correspondent, et accessoirement comment les utiliser. La définition génère du code exécutable (celui que tu retrouve dans ton .o)/ de l'espace réservé (pour les variables globales).
    Lorsque dans MaClasse.cpp, le compilateur rencontre:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    MaClasse::MaFonction()
    {
    blablabla
    }
    Il génère dans MaClasse.o :
    1/ Du code exécutable entre l'adresse XXX et XXXX + N
    2/ Il dit que MaClasse::MaFonction commence à l'adresse XXX

    Lorsque dans main.cpp, le compilateur rencontre :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    MonObjet.MaFonction()
    La déclaration (le .h) ne génère pas de code en tant que telle. Elle indique au compilateur que MaFonction existe et comment l'appel doit se faire. C'est bien le code dans le cpp qui génère l'appel :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    Positionner MonObjet
    Positionner les paramètres
    Appeler MaFonction
    Récupérer le résultat
    MaFonction reste sous forme de symbole.

    L'éditeur de lien remplace partout où il trouve 'Appeler MaFonction' par 'Aller à XXX'

    Comment il s'y retrouve? Parce que les symboles pour identifier MaFonction ne sont pas arbitraire mais suivent une logique : la signature de la méthode.
    Par exemple, le code main.cpp suivant compilé et linké tout seul :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     
    #include <string>
     
    class MaClasse
    {
    public:
       void MaFonction();
       void MaFonction(std::string);
       void MaFonction(int,int,int);
    };
    int main()
    {
       MaClasse a;
       a.MaFonction();
       a.MaFonction("tutu");
       a.MaFonction(1,2,3);
       return 0;
    }
    Te donne trois erreurs de link :
    1>EssaiWin32.obj : error LNK2001: unresolved external symbol "public: void __thiscall MaClasse::MaFonction(int,int,int)" (?MaFonction@MaClasse@@QAEXHHH@Z)
    1>EssaiWin32.obj : error LNK2001: unresolved external symbol "public: void __thiscall MaClasse::MaFonction(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >)" (?MaFonction@MaClasse@@QAEXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z)
    1>EssaiWin32.obj : error LNK2001: unresolved external symbol "public: void __thiscall MaClasse::MaFonction(void)" (?MaFonction@MaClasse@@QAEXXZ)
    1
    Tu vois que tu as trois signatures symboles différents correspondant aux 3 signatures :
    ?MaFonction@MaClasse@@QAEXHHH@Z
    ?MaFonction@MaClasse@@QAEXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z
    ?MaFonction@MaClasse@@QAEXXZ

    En gros, tu retrouve le nom de la classe, le nom de la méthode et celui des paramètres.

Discussions similaires

  1. [DLL/classe template] problème de link
    Par Bob.Killer dans le forum C++
    Réponses: 7
    Dernier message: 31/08/2005, 18h56
  2. Problème de link...
    Par Royd938 dans le forum MS SQL Server
    Réponses: 4
    Dernier message: 30/09/2004, 17h33
  3. C/asm : problème pour link
    Par SteelBox dans le forum Autres éditeurs
    Réponses: 3
    Dernier message: 06/04/2004, 23h03
  4. Problème de LINK Bizarre !!
    Par Jasmine dans le forum MFC
    Réponses: 24
    Dernier message: 19/03/2004, 15h58
  5. Problème de link avec Borland C++ 5.5
    Par gelam dans le forum Autres éditeurs
    Réponses: 5
    Dernier message: 24/11/2003, 16h45

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