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 :

Erreur de débutant - Compilation G++ (macOS)


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Avril 2016
    Messages
    173
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Finistère (Bretagne)

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

    Informations forums :
    Inscription : Avril 2016
    Messages : 173
    Par défaut Erreur de débutant - Compilation G++ (macOS)
    Bonjour, je débute en c++, et là j'essaye de me faire une todo-list en CLI. Je ne vous cache pas que comme je suis débutant, je me passerais bien des objets, mais lorsque j'utilise juste plus de 1 fonction, mon IDE, attend le constructeur des autres fonctions. Bref du coup j'ai fait ce code:

    main.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
    21
    22
    23
    24
    25
    26
    27
    28
    29
    #include <stdlib.h> 
    #include <iostream>
    #include <string>
    #include <fstream>
    #include "../include/main_functions.hpp"
    #include "./todo.cpp"
     
    using namespace std;
     
    int main(int argc, const char *argv[]) {
        //Set global filepath var.
        string const fileName("./todo.txt");
     
        todo item("");
     
     
        if(argc < 2) {
            cout << "Usage: todo <parameter>!" << endl;
            return -1;
        }
        if(std::string(argv[1]) == "todo" && std::string(argv[2]) == "-a") {
            item.add(fileName, argv[3]);
        }
        if(std::string(argv[1]) == "todo" && std::string(argv[2]) == "-d") {
            item.del(fileName, atoi(argv[3]));
        }
     
        return 0;
    }
    todo.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
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    #include <stdlib.h> 
    #include <iostream>
    #include <string>
    #include <fstream>
     
    #include "../include/main_functions.hpp"
     
    using namespace std;
     
    void todo::add(string file, string arg) {
     
        ofstream monFlux(file.c_str(), ios::app);
     
        if(monFlux) {
            monFlux << arg << endl;
        }
        else {
            cout << "ERREUR: Impossible d'ouvrir le fichier." << endl;
        }
    }
     
    void todo::del(string file, int line_erase) {
        std::string Buffer = ""; //Variable contenant le texte à réécrire dans le fichier
        std::ifstream ReadFile(file);
        if (ReadFile) { //Si le fichier est trouvé
            std::string line;
            int Line = 0;
            while (std::getline(ReadFile, line)) { //on parcours le fichier et on initialise line à la ligne actuelle
                Line++;
                if(Line != line_erase) //Si la ligne atteinte est différente de la ligne à supprimer...
                    Buffer += line + "\n"; //On ajoute le contenu de la ligne dans le contenu à réécrire
            }
        }
        ReadFile.close(); //On ferme le fichier en lecture
     
        std::ofstream WriteFile(file); //On ouvre ce même fichier en écriture
        WriteFile << Buffer; //On écris le texte dedans
        WriteFile.close(); //et on ferme le fichier
    }
    main_functions.hpp:
    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
    #ifndef main_functions_hpp
    #define main_functions_hpp
     
    #include <stdlib.h> 
    #include <iostream>
    #include <string>
    #include <fstream>
     
    //functions prototypes
     class todo {
     
     public:
        todo();
        todo(std::string empty);
        void add(std::string file, std::string arg);
        void del(std::string file, int line_erase);
     
    private:
        std::string file, arg;
        int line_erase;
     }; // todo
     #endif /*main_functions_hpp*/
    Et lorsque je compile : g++ main.cpp -o todo, voici ce que j’obtiens :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    Undefined symbols for architecture x86_64:
      "todo::todo(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >)", referenced from:
          _main in main-d3f965.o
    ld: symbol(s) not found for architecture x86_64
    clang: error: linker command failed with exit code 1 (use -v to see invocation)
    Comment puis-je faire ? (Merci d'avance pour votre aide)

  2. #2
    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
    Salut,

    Quand tu veux compiler plusieurs fichiers séparément, de manière à générer les fichiers objets, mais sans provoquer l'édition de liens, tu dois ajouter le paramètre -c à ta ligne de commandes:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    g++ -c main.cpp -o main.o -I. 
    g++ -c todo.cpp -o todo.o -I.
    g++ main.o todo.o -o myexe
    NOTA:
    1- on n'inclut JAMAIS un fichier d'implémentation (*.cpp), NULLE PART (et surtout pas dans un autre fichier d'implémentation). Il n'y a que les fichier d'EN-TETE (*.hpp) qui peuvent être inlus à l'aide de la directive #include.

    2- Idealement, on n'indiquera que le nom du fichier d'en-tête qui devra être inclu (ex : #include <main_functions.hpp> et on indiquera le dossier dans lequel il faut aller les chercher sous forme de paramètres dans la ligne de commande:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    g++ -c main.cpp -o main.o -I../include
    3- La directive using namespace std; ne devrait jamais être utilisée. Les raisons en ont été expliquées en long, en large et en travers sur le forum. Je te laisse faire ta propre recherche pour les comprendre

    4- Idéalement, on va essayer d'avoir une certaine cohérence dans les noms entre les fichiers d'implémentation (*.cpp) et les fichiers d'en-tête (*.hpp). C'est particulièrement vrai lorsque l'on défini une classe : Il est beaucoup plus facile de s'y retrouver lorsque le nom du fichier d'en-tête et du fichier d'implémentation correspond au nom de la classe que l'on crée. Au lieu d'appeler ton fichier d'en-tête main_funcitons.hpp (qui n'est pas un mauvais nom, au demeurant), pourquoi ne l'appellerais tu pas, tout simplement todo.hpp vu qu'il définit... la classe todo

    5- Depuis C++11 (qui date déjà de huit ans maintenant), il y a un constructeur pour std::ifstream (ainsi que pour std::ofstream) qui accepte de recevoir le nom du fichier sous la forme d'une std::string. Il n'y a donc plus besoin de faire appel à la fonction membre c_str de std::string afin de lui transmettre une chaîne de caractères "C style"

    6- Que ce soit pour la classe std::ifstream ou pour la classe std::ofstream, le destructeur s'assure que tout fichier ouvert sera correctement fermé lorsque l'instance de la classe est détruite. Or, si tu crées une instance de l'une ou l'autre de ces classes à l'intérieur d'une fonction, les règles de portée font que cette instance sera détruite (et son destructeur appelé automatiquement) lorsque l'on atteind l'accolade fermante } correspondant à la portée dans laquelle l'instance de la classe a été déclarée.

    Il n'y a donc, a priori, absolument aucune raison d'appeler explicitement la fonction membre close() dans ce cas.


    7- Dés que ton projet nécessite plus d'un fichier pour fournir le résultat final, tu as sans très largement intérêt à "automatiser" la compilation, car les instructions qu'il faudra lancer pour obtenir l'exécutable final vont avoir tendances à se multiplier comme les petits pains et à devenir de plus en plus complexes.

    La solution "classique" est d'utiliser un outil d'automatisation de la compilation appelé make, qui travaille avec un fichier texte décrivant le processus de compilation dans son ensemble appelé Makefile. Mais l'écriture d'un tel fichier devient rapidement longue, fastidieuse et source d'erreurs, surtout lorsque ton projet commence à grandir et à nécessiter des dépendances qui pourraient ne pas être remplie.

    L'idéal est donc de passer par un outil de description et de configuration de ton projet qui pourra, sur base de la description du projet, générer un (ou plusieurs) Makefile. Les outils "historiques" pour une telle descriptions sont appelés autotools sous linux (et regroupent autoconf automake, autoheaders et libtool), mais restent assez rudes à l'emploi (en plus, ils ne sont pas "facilement utilisables" sous windows ).

    Depuis quelques années, nous disposons par contre d'un outil, que l'on peut utiliser aussi bien sous linux, sous mac ou sous windows, appelé CMake qui fait parfaitement le travail -- qui est utilisé entre autres par les IDE clion, et xcode (que l'on trouve sous Mac), du moins, si je ne m'abuse -- mais qui peut également être utilisé en ligne de commandes (si tu tiens absolument à coder "à l'ancienne" ).

    Je te conseillerais vivement de t'intéresser à cet outil, car, même si cela implique qu'il y aura une petite étape d'apprentissage pour être en mesure de l'utiliser, cela te facilitera énormément la vie par la suite.
    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
    Membre confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Avril 2016
    Messages
    173
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Finistère (Bretagne)

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

    Informations forums :
    Inscription : Avril 2016
    Messages : 173
    Par défaut
    Tout d'abord je vous remercie, beaucoup pour votre aide, très précise, et détaillée (ça vous a pris surement pas mal de temps ).

    J'ai modifier le code, mais en vain, l'erreur apparaît toujours, je ne vois pas en quoi j'édite des liens vu qu'à la compilation, j'ai bien rajouter le paramètres : -c, comme vous me l'avez indiqué.
    Mais pourtant cette erreur, est bien-là :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    Undefined symbols for architecture x86_64:
      "todo::todo(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >)", referenced from:
          _main in main.o
    ld: symbol(s) not found for architecture x86_64
    clang: error: linker command failed with exit code 1 (use -v to see invocation)
    main.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
    21
    22
    23
    24
    25
    #include <iostream>
    #include <string>
    #include <fstream>
    #include "../include/todo.hpp"
     
    int main(int argc, const char *argv[]) {
        //Set global filepath var.
        std::string const fileName("./todo.txt");
     
        todo item("");
     
     
        if(argc < 2) {
            std::cout << "Usage: todo <parameter>!" << std::endl;
            return 2;
        }
        if(std::string_view(argv[1]) == "todo" && std::string_view(argv[2]) == "-a") {
            item.add(fileName, argv[3]);
        }
        if(std::string_view(argv[1]) == "todo" && std::string_view(argv[2]) == "-d") {
            item.del(fileName, atoi(argv[3]));
        }
     
        return 0;
    }
    todo.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
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    #include <iostream>
    #include <string>
    #include <fstream>
     
    #include "../include/todo.hpp"
     
    void todo::add(std::string file, std::string arg) {
     
        std::ofstream monFlux(file, std::ios::app);
     
        if(monFlux) {
            monFlux << arg << std::endl;
        }
        else {
            std::cout << "ERREUR: Impossible d'ouvrir le fichier." << std::endl;
        }
    }
     
    void todo::del(std::string file, int line_erase) {
        std::string Buffer = ""; //Variable contenant le texte à réécrire dans le fichier
        std::ifstream ReadFile(file);
        if (ReadFile) { //Si le fichier est trouvé
            std::string line;
            int Line = 0;
            while (std::getline(ReadFile, line)) { //on parcours le fichier et on initialise line à la ligne actuelle
                Line++;
                if(Line != line_erase) //Si la ligne atteinte est différente de la ligne à supprimer...
                    Buffer += line + "\n"; //On ajoute le contenu de la ligne dans le contenu à réécrire
            }
        }
     
        std::ofstream WriteFile(file); //On ouvre ce même fichier en écriture
        WriteFile << Buffer; //On écris le texte dedans
    }
    todo.hpp:
    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
    #ifndef todo_hpp
    #define todo_hpp
     
    #include <stdlib.h> 
    #include <iostream>
    #include <string>
    #include <fstream>
     
    //functions prototypes
     class todo {
     
     public:
        todo();
        todo(std::string empty);
        void add(std::string file, std::string arg);
        void del(std::string file, int line_erase);
     
    private:
        std::string file, arg;
        int line_erase;
     }; // todo
     #endif /*todo_hpp*/
    Pourriez-vous m'éclairer à propos d’où elle peut venir cette fois-ci ?

    Et pour CMake, je suis en train de voir comment je peux l'utiliser avec VSCode (macOS) .

  4. #4
    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
    Et, dis moi: Où elle est l'implémentation du constructeur de ta classe todo qui prend une std::string comme paramètre

    Car là, l'éditeur de liens se plaint de ne pas trouver le code binaire qui correspond à cette fonction, alors que tu y a fait appel dans la fonction main

    Et, en même temps, c'est normal, parce
    • que tu indique en toute lettre qu'un tel constructeur existe à lla ligne 14 du fichier main_functions.hpp.
    • tu y fais spécifiquement appel à la ligne 14 du fichier main.cpp
    • mais tu n'indique nulle part le code qui correspond à cette fonction pour que le compilateur soit en mesure de générer le code exécutable correspondant


    D'ailleurs, tu n'as pas non plus fourni l'implémentation du constructeur de todo qui ne prend aucun paramètre... Si l'éditeur de liens ne s'en plaint pas, c'est uniquement parce que tu n'y fait appel nulle part "par chance"

    Ah, et, au passage : il faut toujours veiller à n'inclure, dans un fichier -- quel qu'il soit -- que les fichiers d'en-tête dont tu as absolument besoin!

    Cela ne fait -- le plus souvent -- pas énormément de mal d'en rajouter qui sont inutiles, mais cela peut ralentir la compilation très rapidement

    Si bien que:
    • Le seul fichier d'en-tête dont tu aies réellement besoin dans main_functions.hpp, c'est <string> (pour que le compilateur sache que std::string existe)
    • En dehors de main_functions.hpp (pour que le compilateur sache que la classe todo existe), le seul fichier d'en-tête dont tu aies réellement besoin dans todo.cpp, c'est le fichier <fstream> pour pouvoir manipuler std::ifstream et std::ofstream, vu que main_functions.hpp inclut déjà <string>
    • En dehors de main_functions.hpp (pour que le compilateur sache que la classe todo existe), tu n'as besoin d'aucun autre fichier d'en-tête dans main.cpp, vu que la seule chose que tu utilises à part la classe todo, c'est la classe std::string, et que <string> est déjà inclut dans main_functions.hpp

    Tu ne verras sans doute aucune diminution sensible du temps de compilation sur ce projet particulier, mais tu connait le proverbe:
    Ce sont les petits ruisseaux qui produisent les gros fleuves
    Quand tu travailleras sur un projet de plusieurs centaines ou plusieurs milliers de fichiers, le temps gagné en n'incluant pas toute sortes de fichiers d'en-tête inutiles peut de faire gagner de précieuses minutes
    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. #5
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 493
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 493
    Billets dans le blog
    1
    Par défaut
    N'inclure que le minimum évite aussi les programmes qui compilent par chance.... et qui un jour ne compilent plus par malchance !

    Imagine que tu inclues Foo.hpp qui inclue Bar.hpp qui inclue <vector> alors que ni Foo.hpp ni Bar.hpp n'utilisent <vector>. Dans ton fichier cpp, tu utilises std::vector et ça compile parfaitement : ben oui, tu as inclus quelqu'un qui a inclus quelqu'un qui a inclus <vector> à ta place. Un jour, ces quelqu'uns font du ménage : ton code ne compile plus et tu te prends la tête.

  6. #6
    Membre confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Avril 2016
    Messages
    173
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Finistère (Bretagne)

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

    Informations forums :
    Inscription : Avril 2016
    Messages : 173
    Par défaut
    Merci beaucoup pour votre réponse

    Mais non, en fait l'objet n'était pas adapté à mon projet, en tout cas pour le moment.

    J'ai donc fait ceci à la place, en utilisant simplement des fonctions void.

    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
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    #include <iostream>
    #include <string>
    #include <fstream>
    #include <stdio.h>
     
    void add(std::string file, std::string prm) {
        std::ofstream monFlux(file, std::ios::out);
     
        if(monFlux) {
            monFlux << prm << std::endl;
        }
        else {
            std::cout << "ERREUR: Impossible d'ouvrir le fichier." << std::endl;
        }
    }
    void del(std::string file, int ln) {
        std::string Buffer = ""; //Variable contenant le texte à réécrire dans le fichier
        std::ifstream ReadFile(file);
        if (ReadFile) { //Si le fichier est trouvé
            std::string line;
            int Line = 0;
            while(std::getline(ReadFile, line)) { //on parcours le fichier et on initialise line à la ligne actuelle
                Line++;
                if(Line != ln) //Si la ligne atteinte est différente de la ligne à supprimer...
                    Buffer += line + "\n"; //On ajoute le contenu de la ligne dans le contenu à réécrire
            }
        } else {
            std::cout << "ERREUR: Impossible d'ouvrir le fichier." << std::endl;
        }
     
        std::ofstream WriteFile(file); //On ouvre ce même fichier en écriture
        WriteFile << Buffer; //On écris le texte dedans
    }
    void ls(std::string file) {
        std::ifstream ReadFile(file);
        if(ReadFile) {
            std::string line;
            while(std::getline(ReadFile, line)) {
                    std::cout << line << std::endl;  // on l'affiche
            }
        } else {
            std::cout << "ERREUR: Impossible d'ouvrir le fichier." << std::endl;
        }
    }
     
    int main(int argc, const char *argv[]) {
        //Set global filepath var.
        std::string const fileName("./todo.txt");
     
        if(argc < 2) {
            std::cout << "Usage: todo <parameter>!" << std::endl;
            return -1;
        }
        if(std::string(argv[1]) == "todo" && std::string(argv[2]) == "-a") {
            add(fileName, argv[3]);
        }
        if(std::string(argv[1]) == "todo" && std::string(argv[2]) == "-d") {
            del(fileName, atoi(argv[3]));
        }
        if(std::string(argv[1]) == "todo" && std::string(argv[2]) == "-ls") {
            ls(fileName);
        }
     
        return 0;
    }
    Ca fonctionne, seulement le gros casse-tête dans tout sa, est que lorsque j'ajoute une phrase complète via mon programme, il ne m'ajoute au fichier todo.txt que l'argument qui suit argv[2], donc à savoir argv[3], mais du coup tous le reste de ma phrase est ignoré, et n'est donc pas inscrit dans le fichier todo.txt . Je cherche donc une solution pour qu'il ne considère pas le reste de ma phrase comme plusieurs arguments, mais seulement 1 seul. J'ai pensé que mettre entre guillemets, la phrase serait peut-être plus simple à considérer comme un seul arguments, mais pour l'instant je n'ai pas de solution...

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

Discussions similaires

  1. Erreur de compilation => Erreur de débutant
    Par TonyRc dans le forum VBA Access
    Réponses: 0
    Dernier message: 27/05/2008, 12h01
  2. Réponses: 2
    Dernier message: 09/12/2006, 14h42
  3. [Débutant] [Compilation] Avertissement deprecated
    Par javamantools dans le forum Langage
    Réponses: 2
    Dernier message: 08/07/2005, 15h33
  4. [Débutant]Compilation d'un fichier .java
    Par adilou1981 dans le forum Eclipse Java
    Réponses: 2
    Dernier message: 15/04/2005, 14h46
  5. Erreurs à la compilation
    Par Code source dans le forum GLUT
    Réponses: 11
    Dernier message: 02/05/2004, 19h33

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