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 :

[Nioub] gestion de la mémoire


Sujet :

C++

  1. #1
    Membre éclairé Avatar de grabriel
    Inscrit en
    Septembre 2006
    Messages
    946
    Détails du profil
    Informations forums :
    Inscription : Septembre 2006
    Messages : 946
    Points : 730
    Points
    730
    Par défaut [Nioub] gestion de la mémoire
    Bonjour,

    Dans ma quête d'apprentissage de fonctionnement de la gestion de la mémoire je suis tombé sur un problème (tout simple) que je ne comprends pas.

    Voici mon code qui fonctionne :

    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
    #include <iostream.h>
     
    typedef struct unStruct {
     
     char   *unChar;
     
    } unStruct;
     
     
    int main ()
    {
        unStruct *is, tra;
     
        is=&tra;
     
        char *aa = is->unChar;
     
        aa = "data\n";
     
        cout << is->unChar;
     
        cout << aa;
     
        strcpy(is->unChar,"aaa\n");
     
        cout << is->unChar;
     
        is->unChar = aa;
     
        cout << is->unChar;
     
        return 0;
    }
    Ce que je n'ai pas compris c'est la ligne is->unChar = aa; il suffit de la placer au dessus de strcpy(is->unChar,"aaa\n"); pour que ca plante!!!
    Et la je ne comprends pas pourquoi???!!!!

    Si quelqu'un pouvait m'éclairer sur cette erreur, merci!

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

    Informations professionnelles :
    Activité : aucun

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

    Une petite remarque, en passant, pour commencer:

    En C++, la définition de type utilisateurs provoque automatiquement la création du type correspondant. Il est donc inutile de s'amuser à "jouer" avec les typedef à la manière de ce qui se fait en C.

    Ainsi, ton code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    typedef struct unStruct {
     
     char   *unChar;
     
    } unStruct;
    peut il avantageusement être simplifié en
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    struct unStruct {
     
     char   *unChar;
     
    };
    Et, bien sur, ce qui est valable à ce niveau pour struct l'est pour union, class et enum

    De la même manière, il est préférable d'inclure les fichiers d'en-tête sans extension pour les classes fournies par le standard.

    En effet, les versions qui utilisent l'extension .h ne sont fournis que pour des raison "historiques" et datent d'avant la norme (qui date de suffisemment longtemps pour être respectée par tous les compilateurs actuels)

    La seule chose à laquelle il faille penser, c'est que dans les fichiers d'en-tête sans extension, l'ensemble des classes se trouvent dans l'espace de nom std ...

    Cela implique d'écrire std::cout ou, pour autant que ce ne soit pas dans un fichier d'en-tête, d'ajouter la directive using namespace std; ... mais rien d'insurmontable
    Ceci dit, rappelle toi toujours qu'un pointeur représente... l'adresse à laquelle se trouve l'élément en question.

    Au vu de la syntaxe que tu utilise, tu semble venir en droite ligne du C, et les principes qui régissent les pointeurs sont exactement identiques en C qu'en C++.

    La petite piqure de rappel est donc:
    Tant que tu n'a pas fourni une adresse valide à un pointeur, il risque de pointer sur tout et n'importe quoi.
    De plus, n'oublie pas - car les principes de base du C restent valides - que lorsque tu écris un code du genre de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
        char *aa = is->unChar; (1)
        aa = "data\n"; (2)
    voici, en fait ce que tu fais:

    • (1) tu déclare un pointeur de type char;et tu l'initialise avec la valeur (autrement dit l'adresse représentée par) de is->unChar, qui, à ce moment là, peut être tout et n'importe quoi
    • (2) tu décide que aa pointe non plus sur la meme chose que is->unChar, mais bel et bien sur l'adresse ("read only") à laquelle on trouve la chaine constante "data\n"

    Par la suite, toute tentative d'accès à aa sera reportée sur cette adresse "read only" (OK en lecture, BOUM en écriture )

    Quand, plus loin, tu invoque
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
     strcpy(is->unChar,"aaa\n");
    tu demande de placer "à l'adresse qui est signalée par is->unChar" le premier 'a' de "aaa\n", et de placer les suivants à la suite...

    Sauf que, voilà... is->unChar n'est toujours pas correctement initialisé, et contient toujours "les crasses laissées par une utilisation antérieure" de la mémoire.

    Dés lors, ce serait un sérieux coup de malchance que la copie de cette chaine de caractères se passe sans encombre

    Quand, juste après, tu invoque cout<<is->unChar, tu crois obtenir quelque chose qui ressemble à une adresse valide... sauf que tu n'a jamais effectué la réservation de cette adresse.

    Quand, tu en arrive à invoquer is->unChar = aa, tu as enfin une adresse valide pour ton pointeur (mais attention, cette adresse est dans la mémorie read-only)

    Le dernier cout ne pose alors plus de problème.

    La conclusion générale de tout cela, c'est que, encore une fois, il faut être particulièrement attentif à la gestion des pointeurs, et de la mémoire sur laquelle ils pointent.

    Les sacrosaints principes d'allocation dynamique et de libération de la mémoire allouée dynamiquement, et d'initialisation des pointeurs restent en tout état de cause toujours présent, même si new fait un peu plus que malloc et que delete fait un peu plus que free

  3. #3
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    4 265
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 4 265
    Points : 6 686
    Points
    6 686
    Billets dans le blog
    2
    Par défaut
    Bonjour,

    avant toute chose, les remarques habituelles:
    - <iostream.h> est déprécié, il faut utiliser <iostream>
    - l'utilisation de char* est déconseillée en c++, on lui préfèrera infiniment l'utilisation de la classe string de la SL
    - strcpy() est une fonction C, ce n'est pas du c++.
    - en c++, il n'est pas nécessaire d'utiliser typedef lorsque tu déclares une structure.

    Maintenant, voyons ce qu'il se passe dans l'horrible code (difficile de faire pire en vérité) que tu nous a posté:

    Ici tu déclare deux variables:
    is qui est un pointeur sur un objet de type unStruct
    tra qui est un objet de type unStruct. Attention, tra n'est pas un pointeur. Ton code montre pourquoi il est préférable de ne déclarer qu'une seule variable par ligne.

    Ici on fait 2 choses: On déclare une variable aa de type char* et on affecte cette dernière à is->unChar. aa est donc un pointeur vers un tableau de char, lequel est contenu par ton objet is. Notons au passage que ni aa ni is->unChar n'ont été alloué, ce qui ouvre la porte à toutes les erreurs possibles et imaginables.

    Ici, on ré-affecte la variable aa. Cette ligne anule la ligne précédente. Et on l'affecte à "data\n", qui est ce que l'on appelle une variable temporaire non nommée. A l'exécution, une partie de la mémoire sera alloué sur la pile pour placer la chaine "data\n", qui sera désallouée lorsque l'exécution sortira du bloc. Une temporaire non nomée (ou rvalue) n'est pas modifiable. Donc après cette ligne, aa pointe sur une zone mémoire non modifiable.

    Ici tu fais pointer is->unChar sur aa, c'est à dire sur une zone mémoire non modifiable.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    strcpy(is->unChar,"aaa\n");
    Et là tu essaie de modifier cette zone mémoire => crash!


    [edit] lol, koala01 plus rapide que l'éclair
    Je suis rassuré de constater qu'on dit à peu près la même chose

    [edit2][HS] J'ai l'impression que ce code est un exemple de ce qu'il ne faut pas faire, en fait.

  4. #4
    Membre éclairé Avatar de grabriel
    Inscrit en
    Septembre 2006
    Messages
    946
    Détails du profil
    Informations forums :
    Inscription : Septembre 2006
    Messages : 946
    Points : 730
    Points
    730
    Par défaut
    Citation Envoyé par r0d
    Maintenant, voyons ce qu'il se passe dans l'horrible code (difficile de faire pire en vérité) que tu nous a posté:
    Et moi qui était tout content que ca compile.

    Merci pour vos explications, très complète.

    C'est pas encore trop claire, mais je sens que ça va venir.... à force de me faire répéter et de lire ça finira bien par rentrer.

    Je retourne lire mon bouquin de Stroustrup, les tutos et les faq.

  5. #5
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 381
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 381
    Points : 41 582
    Points
    41 582
    Par défaut
    Comportement indéterminé: is->unChar est un pointeur non-initialisé, et l'opérateur << (ostream &, char*) va tenter de le déréférencer.
    À partir de ce point, tout peut arriver.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 629
    Points : 30 692
    Points
    30 692
    Par défaut
    Citation Envoyé par r0d Voir le message
    <snip>
    [edit] lol, koala01 plus rapide que l'éclair
    Je suis rassuré de constater qu'on dit à peu près la même chose
    De fait, c'est rassurant, pour tout le monde: ca signifie que l'on de dit pas trop de bêtises
    Citation Envoyé par grabriel Voir le message
    Et moi qui était tout content que ca compile.

    Merci pour vos explications, très complète.
    Retiens toujours qu'un code qui compile sans erreur ne veut absolument pas dire que l'application est exempte de bugs: cela signifie - au mieux - que le compilateur n'a pas trouvé d'erreur de syntaxe ( que tout ce que tu as écrit a été compris par le compilateur).

    Ceci dit, un compilateur bien réglé t'aurait surement affiché quelques avertissements.

    Ainsi, chez moi, il m'affiche
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    D:\projects\forumcpp\main.cpp||In function 'int main()':|
    D:\projects\forumcpp\main.cpp|19|attention : deprecated conversion from string constant to 'char*'|
    c:\mingw\lib\gcc\..\..\include\c++\4.3.0\ostream|514|attention : 'tra.unStruct::unChar' is used uninitialized in this function|
    D:\projects\forumcpp\main.cpp|13|note: 'tra.unStruct::unChar' was declared here|
    ||=== Build finished: 2 errors, 0 warnings ===|
    Les avertissements sont à la limite plus important à prendre en compte que les erreurs:

    Une erreur empêche le compilateur de terminer son travail, et tu sais donc qu'il faut "changer quelque chose".

    Les avertissements te signalent que tu fais quelque chose qui est "potentiellement dangereux", à tel point que même le compilateur est capable de s'en rendre compte, dans son contexte de travail

    Si on peut "glisser" sur ce qui est déprécié (ce n'est qu'un avertissement qui dit que le compilateur aurait préféré que tu fasse de manière plus "moderne"), il faut être très attentif à ceux qui te signalent que quelque chose n'est pas initiallisé, et aux notes qui suivent.

    Ils traduisent en effet le plus souvent une erreur de logique qui peut mener à des catastrophes.

    Au final, tu a de la chance que l'application se "contente" de planter subitement ainsi... tu aurais pu lancer une bombe nucléaire sur moscou sans le savoir

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

Discussions similaires

  1. Réponses: 17
    Dernier message: 02/02/2006, 12h03
  2. gestion de la mémoire
    Par moldavi dans le forum C++
    Réponses: 17
    Dernier message: 04/02/2005, 23h18
  3. Réponses: 11
    Dernier message: 26/12/2004, 22h50
  4. Gestion de la mémoire entre plusieurs DLL
    Par Laurent Gomila dans le forum C++
    Réponses: 7
    Dernier message: 27/07/2004, 15h28
  5. Gestion des variables - mémoire ?
    Par RIVOLLET dans le forum Langage
    Réponses: 4
    Dernier message: 26/10/2002, 12h44

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