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 segmentation


Sujet :

C++

  1. #1
    Membre à l'essai Avatar de CaptainKrabs
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2018
    Messages
    23
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2018
    Messages : 23
    Points : 20
    Points
    20
    Par défaut Erreur de segmentation
    Bonjour ,

    La dernière fois, un collègue me parlait d'erreur de segmentation et m'a décrit l'exemple suivant :


    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
    #include <iostream>
    #include <string>
     
    // Exemple de code qui créé une erreur de segmentation.
     
    std::string &calcul(int valeur)
    {
            valeur++;
            std::string retour = std::to_string(valeur);
            return retour;
    }
     
    int main()
    {
            std::cout << calcul(3) << std::endl;
    }
    Lors de d'exécution ce code produit une erreur de segmentation : Segmentation fault (core dumped).

    Dans un premier temps, voici ce que je pense, concernant l'origine de l'erreur.

    La fonction calcul retourne une référence sur un type string.
    Dans cette fonction, la variable retour (type string) est déclarée et est renvoyée.
    Or, un string correspondant à un tableau de type char, c'est donc la référence vers le premier élément du tableau qui est renvoyée (type char&).
    La variable retour est une variable locale donc stockée dans la heap et cette adresse n'existe plus dès que l'on sort de la fonction.
    Ainsi lorsque la fonction est appelée dans main, la case mémoire est invalide ce qui provoque l'erreur de segmentation.

    Mon collègue m'a également dit que cette erreur pouvait être corrigé via l'usage du mot clé const. Le problème est que je ne vois pas bien comment utilisé const dans mon code pour corriger cette erreur.
    J'ai bien essayé de l'utiliser dans la définition de ma fonction et le type de retour de la sorte :
    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
    #include <iostream>
    #include <string>
     
    // Exemple de code qui créé une erreur de segmentation.
     
    const std::string &calcul(int valeur)
    {
            valeur++;
            const std::string retour = std::to_string(valeur);
            return retour;
    }
     
    int main()
    {
            std::cout << calcul(3) << std::endl;
    }
    Cependant, j'obtiens toujours l'erreur de segmentation. J'ai l'impression que l'idée est faire en sorte que la référence reste en vie en utilisant const mais je ne vois pas bien comment faire.
    Auriez-vous des pistes à me suggérer svp ?

  2. #2
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 677
    Points
    13 677
    Billets dans le blog
    1
    Par défaut
    Salut

    Or, un string correspondant à un tableau de type char, c'est donc la référence vers le premier élément du tableau qui est renvoyée (type char&).
    Cette affirmation est fausse. En C, les chaines sont effectivement des tableaux de char mais std::string n'est pas du C et ce n'est pas équivalent à un tableau de char. Tu renvoie donc bien une référence vers une variable de type std::string. Le reste de l'analyse se tient en revanche : tu renvoies l'adresse (via la référence) d'une variable locale à une fonction, et cette référence n'est pas valide dans le contexte de l'appelant, l'objet ayant été détruit quand on s'en sert.


    Le 2e code compile avec un warning :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    <source>: In function 'const std::string& calcul(int)':
    <source>:10:16: warning: reference to local variable 'retour' returned [-Wreturn-local-addr]
       10 |         return retour;
          |                ^~~~~~
    Je me demande si ton collègue ne voulait pas faire un code comme celui montré ici : https://herbsutter.com/2008/01/01/go...portant-const/

    Ca donnerait ça :

    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
    #include <iostream>
    #include <string>
     
    std::string calcul(int valeur)
    {
            valeur++;
            const std::string retour = std::to_string(valeur);
            return retour;
    }
     
    int main()
    {
        const auto& result = calcul(3);
        std::cout << "calcul="<< result << std::endl;
    }
    En effet, les const& permettent une "lifetime extesion" de certains objets. Voir https://stackoverflow.com/questions/...ime-of-rvalues (le lien précédent a été trouvé là-bas)

  3. #3
    Membre à l'essai Avatar de CaptainKrabs
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2018
    Messages
    23
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2018
    Messages : 23
    Points : 20
    Points
    20
    Par défaut
    D'accord merci pour cette réponse.

    Cependant il y a encore quelque chose que je ne comprends pas il s'agit du retour de la fonction calcul.
    Dans mon code, la signature de la fonction indique qu'elle renvoie une référence sur un string. Or dans l'implémentation c'est la string retour qui est renvoyée et non sa référence... Le code fonctionne pourtant...Je ne comprends pas ?

    Je vois d'ailleurs que dans ton code tu as supprimé l'esperluette lors de la déclaration de la fonction.

  4. #4
    Membre expérimenté
    Femme Profil pro
    ..
    Inscrit en
    Décembre 2019
    Messages
    582
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 94
    Localisation : Autre

    Informations professionnelles :
    Activité : ..

    Informations forums :
    Inscription : Décembre 2019
    Messages : 582
    Points : 1 305
    Points
    1 305
    Par défaut
    Salut,

    C'est une règle de C++03 qui dit qu'on peut étendre la vie d'une temporaire en la référençant avec une const lvalue, d'où la notation const type& var= function(), mais seulement sous certaines conditions, et c'est pour ça qu'il est peut-être préférable de s'en remettre à la RVO (Return Value Optimization) et depuis C++11 la NRVO (Named RVO) et la Copy Elision.

    https://pvs-studio.com/en/blog/posts/cpp/1006/
    https://abseil.io/tips/107

  5. #5
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 677
    Points
    13 677
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par CaptainKrabs Voir le message
    Dans mon code, la signature de la fonction indique qu'elle renvoie une référence sur un string. Or dans l'implémentation c'est la string retour qui est renvoyée et non sa référence
    Une référence est un alias sur une variable. On s'en sert comme une variable.

    Quand tu fais return foo;, le compilateur regarde le type de retour :

    • si c'est Foo, il renvoie une copie de la variable (avec le RVO et NRVO, cette copie n'a pas vraiment lieu)
    • Si c'est Foo&, il crée une référence


    Tu n'as pas besoin de créer une référence à partir d'une variable comme tu crées un pointeur avec & pour prendre l'adresse de la variable.

    Citation Envoyé par CaptainKrabs Voir le message
    Je vois d'ailleurs que dans ton code tu as supprimé l'esperluette lors de la déclaration de la fonction.
    Oui car renvoyer l'adresse (via une référence ou via un pointeur) d'une variable locale n'a pas de sens. Il faut renvoyer une instance.

    Note que le lifetime extension n'augmente pas la durée de vie de la variable dans la fonction, mais bien de celle retournée. On peut le visualiser avec des traces dans les constructeurs / destructeurs. Le code suivant devrait illustrer le propos :

    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
    #include <iostream>
     
    struct Foo {
        Foo() {
            std::cout << "--constructor" << '\n';
        }
     
        ~Foo() {
            std::cout << "--destructor" << '\n';
        }
     
    };
     
    static std::ostream& operator<<(std::ostream& os, const Foo& foo) {
        os << "Foo @" << &foo;
        return os;
    }
     
    static Foo returnCopy() {
        std::cout << "Create foo..." << '\n';
        Foo foo;
        std::cout << "Return foo..." << '\n';
        return foo;
    }
     
    int main() {
        {
            std::cout << "with extension" << '\n';
            const auto& extended = returnCopy();
            std::cout << extended << '\n';
            std::cout << "end" << '\n';
        }
        std::cout << '\n' << '\n';
     
        {
            std::cout << "with no extension" << '\n';
            std::cout << returnCopy() << '\n';
            std::cout << "end" << '\n';
        }
    }
    with extension
    Create foo...
    --constructor
    Return foo...
    Foo @0x67525ffa16
    end
    --destructor


    with no extension
    Create foo...
    --constructor
    Return foo...
    Foo @0x67525ffa17
    --destructor
    end
    Ce qui est intéressant, c'est de voir que end et --destructor sont inversés.

    PS : On peut modifier le code précédent pour renvoyer une référence. GCC émet des warnings de compilation (déjà, premier signe). La sortie du programme devient :

    with extension
    Create foo...
    --constructor
    Return foo...
    --destructor
    Foo @0
    end


    with no extension
    Create foo...
    --constructor
    Return foo...
    --destructor
    Foo @0
    end
    Dans les 2 cas, on voit que l'objet est détruit avant qu'on tente de l'afficher.

  6. #6
    Membre à l'essai Avatar de CaptainKrabs
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2018
    Messages
    23
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2018
    Messages : 23
    Points : 20
    Points
    20
    Par défaut
    D'accord merci beaucoup, je comprends mieux à présent.

    J'ai essayé d'exécuter le code montré ci-dessus et je vois que l'objet Foo est, dans mon cas, créé à la même adresse. Mais j'imagine que cela dépend simplement de la manière dont le programme gère la mémoire.

    Pièce jointe 637255

    Merci !
    Images attachées Images attachées  

  7. #7
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 677
    Points
    13 677
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par CaptainKrabs Voir le message
    D'accord merci beaucoup, je comprends mieux à présent.
    De rien. Cool !

    Citation Envoyé par CaptainKrabs Voir le message
    Mais j'imagine que cela dépend simplement de la manière dont le programme gère la mémoire.
    Oui.

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

Discussions similaires

  1. Erreurs de segmentation !
    Par anti-conformiste dans le forum Applications et environnements graphiques
    Réponses: 16
    Dernier message: 18/10/2005, 11h11
  2. Erreur de segmentation
    Par Trunks dans le forum C
    Réponses: 3
    Dernier message: 06/10/2005, 18h28
  3. Erreur de segmentation (Inconnue)
    Par Dark-Meteor dans le forum C
    Réponses: 5
    Dernier message: 08/09/2005, 13h42
  4. [Dev-C++] Erreur de segmentation...
    Par sas dans le forum Dev-C++
    Réponses: 11
    Dernier message: 26/03/2005, 14h25
  5. erreur de segmentation
    Par transistor49 dans le forum C++
    Réponses: 10
    Dernier message: 15/03/2005, 11h18

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