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 :

Incompréhension dans la libération de pointeur


Sujet :

C

  1. #1
    Nouveau Candidat au Club
    Homme Profil pro
    Enseignant Chercheur
    Inscrit en
    Janvier 2015
    Messages
    3
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Chili

    Informations professionnelles :
    Activité : Enseignant Chercheur

    Informations forums :
    Inscription : Janvier 2015
    Messages : 3
    Points : 1
    Points
    1
    Par défaut Incompréhension dans la libération de pointeur
    Bonjour à tous,

    Je sais que c'est une question récurrentes mais j'ai besoin d'un peu d'aide. j'alloue un tableau 2d de pointeurs (int) puis je le libère. Et lorsque j'appelle de nouveau celui-ci il semble toujours le reconnaître. J'ai suivit le tutoriel suivant: http://rperrot.developpez.com/articles/c/allocationC/

    Un exemple vaut mieux qu'un discours:

    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
    #include<stdio.h>
    #include<stdlib.h>
     
    int main(){
     
     int k;
     int **M;
     
     // Array of int (contiguous allocation), 2 rows x 3 colums.
     
     M = (int **) malloc( 2*sizeof(int*) );
     
     M[0] = (int *)malloc( 2*3*sizeof(int) );
     
     for (k=1;k<2;k++) M[k] = M[k-1] + 3;
     
     // values in the array
     
     M[0][0] = 1.;
     M[0][1] = 2.;
     M[0][2] = 3.;
     
     M[1][0] = 4.;
     M[1][1] = 5.;
     M[1][2] = 6.;
     
     // free 
     free(M[0]);
     M[0] == NULL;
     free(M);
     M == NULL;
     
     // print some values
     printf("%d %d %d \n",M[0][0],M[0][1],M[0][2]);
     printf("%d %d %d \n",M[1][0],M[1][1],M[1][2]);
     
     return 0;
    }
    Lors de l'execution j'obtiens des 0 ou des chiffres "aleatoires" pour la première ligne et 4 5 6 pour la seconde. La mémoire est-elle bien libérée?

    Si j'ajoute dans la section // free:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    free(M[1]);
     M[1] == NULL;
    j'obtiens l'erreur suivante:

    *** Error in `./test': free(): invalid pointer: 0x0000000000c9003c ***
    Abandon

    Je précise que la manière dont j'alloue la mémoire est faite pour avoir une allocation continue des blocs (compatibilité future avec fortran).

    Merci d'avance pour vos lumières.

  2. #2
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 368
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 368
    Points : 23 622
    Points
    23 622
    Par défaut
    Bonjour et bienvenue,

    Tu as initialisé M[0] avec la valeur renvoyée par malloc() mais tu n'as jamais rien écrit dans M[1]. La valeur contenue à cet emplacement est donc complètement indéfinie. La libérer avec free() ne peut conduire qu'à un plantage. Quand tu écris « malloc( 2*3*sizeof(int) ); », tu alloues de la place pour six entiers consécutifs « quelque part en mémoire » et tu demandes à ton programme de consigner dans M[0] l'emplacement du début de ce « quelque part ».

    Il se trouve que cet espace est complètement indépendant de celui occupé par ton tableau qui, lui, a été alloué à l'étape précédente et qui peut se trouver n'importe où lui aussi.

    En outre, même si tu as écrit « 2 * 3 * sizeof(…) », il s'agit d'une expression dont tous les termes sont constants et connus : 2 × 3 × sizeof(int) = 6 × sizeof(int) = 6 × 4 = 24. Ils sont donc directement résolus à la compilation et dans le code exécutable final, tu obtiens directement « malloc(24) ». Aucune déduction n'est faite sur le plan formel en estimant que l'opérateur de multiplication indique qu'il s'agit d'un tableau.

    Par ailleurs, tableaux et pointeurs sont deux choses différentes même si, la plupart du temps, on peut indexer les éléments qu'ils pointent ou qu'ils contiennent de la même façon. C'est justement lorsque l'on travaille avec plus d'une dimension que les difficultés commencent à apparaître. Lorsque tu déclares un tableau, que ce soit à une ou à plusieurs dimensions, tous ses éléments sont consécutifs en mémoire. Le C retrouve l'élément concerné parce qu'il connaît à la fois l'emplacement du début et les dimensions du tableau (seule la plus grande est optionnelle) : une simple multiplication permet de déduire son emplacement. Par contre, quand tu pars d'un pointeur sur un pointeur (« int ** »), celui-ci se comporte en fait comme un tableau mono-dimensionnel de pointeurs. Il faut donc initialiser chacun des pointeurs qu'il contient vers une zone mémoire distincte, et penser à libérer chacune d'entre elles à la fin.

    Si tu veux exploiter une zone mémoire consécutive allouée en une seule fois comme tu le ferais avec un tableau, alors il faudrait déclarer à la un pointeur vers un tableaux d'entiers privé de sa dimension la plus grande.

  3. #3
    Nouveau Candidat au Club
    Homme Profil pro
    Enseignant Chercheur
    Inscrit en
    Janvier 2015
    Messages
    3
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Chili

    Informations professionnelles :
    Activité : Enseignant Chercheur

    Informations forums :
    Inscription : Janvier 2015
    Messages : 3
    Points : 1
    Points
    1
    Par défaut
    Merci beaucoup pour ta réponse. Ca n'est pas encore très clair, pardon. Je cite dans le texte...

    Tu as initialisé M[0] avec la valeur renvoyée par malloc() mais tu n'as jamais rien écrit dans M[1]. La valeur contenue à cet emplacement est donc complètement indéfinie.
    A la ligne 15 du code je dis à M[1] de pointer sur l'adresse qui est à 3 emplacement après celle pointer par M[0].

    Il se trouve que cet espace est complètement indépendant de celui occupé par ton tableau qui, lui, a été alloué à l'étape précédente et qui peut se trouver n'importe où lui aussi.
    Oui en fait je veux juste allouer un "vecteur" d'entier en mémoire et ailleurs un vecteur de pointeurs dont le premier element pointe sur le debut du vecteur d'entier et les suivants sur des emplacements spécifique de ce vecteur d'entier

    J'alloue en mémoire "quelque part" un tableau de pointeur qui est
    | M[0] | M[1] |

    puis j'alloue un vecteur de 6 entiers "quelque part"

    | x1 | x2 | x3 | x4 | x5 | x6 |

    et je dis de mettre son adresse dans M[0]. Mais ensuite je dis de mettre l'adresse ou se trouve x4 dans M[1]. Est-ce ca? Ce qui fait bien de M[1] un pointeur vers quelques chose...

    Par ailleurs, tableaux et pointeurs sont deux choses différentes même si, la plupart du temps, on peut indexer les éléments qu'ils pointent ou qu'ils contiennent de la même façon. C'est justement lorsque l'on travaille avec plus d'une dimension que les difficultés commencent à apparaître. Lorsque tu déclares un tableau, que ce soit à une ou à plusieurs dimensions, tous ses éléments sont consécutifs en mémoire.
    Je ne veux surtout pas faire:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     M[0] = malloc(3*sizeof(int));
    M[1] = malloc(3*sizeof(int));
    car je ne m'assure pas de la continuité mémoire. En fait dans mon code M[0][3] = M[1][0]! Enfin si j'ai tout compris... puisque

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     printf("%d %d %d %d %d %d\n",M[0][0],M[0][1],M[0][2], M[0][3],M[0][4],M[0][5],M[0][6]);
     printf("%d %d %d %d %d %d\n",M[0][0],M[0][1],M[0][2],M[1][0],M[1][1],M[1][2]);
    donne bien le même résultat (1 2 3 4 5 6).

    Donc en gros, j'ai déclaré un tableau de 2 pointeurs (M contient son adresse) dans le quel je mets des adresses mémoires (M[0] et M[1]), M[0] pointe sur un tableau alloué de 6 entiers, puis M[1] pointe sur l'adresse du 3ème entier de ce tableau.
    Au final je n'ai alloué que 2 tableaux, l'un de pointeur M, l'autre d'entier M[0]. je les libères avec free(). ce que je ne comprends pas, c'est que après libération si je fait encore

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     printf("%d %d %d %d %d %d\n",M[0][0],M[0][1],M[0][2], M[0][3],M[0][4],M[0][5],M[0][6]);
     printf("%d %d %d %d %d %d\n",M[0][0],M[0][1],M[0][2],M[1][0],M[1][1],M[1][2]);
    j'obtiens alors (0 0 33 0 0 0) pour la première ligne et (0 0 33 4 5 6) pour la 2nde. Normal je dirais j'ai désalloué M[0] et M donc l'espace mémoire à pu être réutilisé... Mais dans la 2ème ligne pour la partie M[1], j'ai encore les valeurs originales. La question est: est-ce que la mémoire est réellement libéré? i.e. n'importe quel programme peut reprendre ces blocs mémoires. Pourquoi M[1] pointe encore sur les valeurs? juste du hasard? Rien n'a pris la place entre temps?


    Merci de votre patience

  4. #4
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 368
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 368
    Points : 23 622
    Points
    23 622
    Par défaut
    Citation Envoyé par roukinator Voir le message
    A la ligne 15 du code je dis à M[1] de pointer sur l'adresse qui est à 3 emplacement après celle pointer par M[0].
    Effectivement, j'avais lu cette ligne trop vite. Du coup, ta boucle ne fait qu'une seule passe.
    J'ai cru sur le moment qu'elle servait à initialiser le contenu de ton tableau.

    Dans ce cas, faire un free() sur M[1] conduit nécessairement à un plantage puisque le pointeur ne correspond pas à une des valeurs renvoyées par malloc().

    Donc en gros, j'ai déclaré un tableau de 2 pointeurs (M contient son adresse) dans le quel je mets des adresses mémoires (M[0] et M[1]), M[0] pointe sur un tableau alloué de 6 entiers, puis M[1] pointe sur l'adresse du 3ème entier de ce tableau. Au final je n'ai alloué que 2 tableaux, l'un de pointeur M, l'autre d'entier M[0]. je les libères avec free(). ce que je ne comprends pas, c'est que après libération si je fait encore[

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     printf("%d %d %d %d %d %d\n",M[0][0],M[0][1],M[0][2], M[0][3],M[0][4],M[0][5],M[0][6]);
     printf("%d %d %d %d %d %d\n",M[0][0],M[0][1],M[0][2],M[1][0],M[1][1],M[1][2]);
    j'obtiens alors (0 0 33 0 0 0) pour la première ligne et (0 0 33 4 5 6) pour la 2nde. Normal je dirais j'ai désalloué M[0] et M donc l'espace mémoire à pu être réutilisé... Mais dans la 2ème ligne pour la partie M[1], j'ai encore les valeurs originales. La question est: est-ce que la mémoire est réellement libéré? i.e. n'importe quel programme peut reprendre ces blocs mémoires. Pourquoi M[1] pointe encore sur les valeurs? juste du hasard? Rien n'a pris la place entre temps?
    C'est dû au fait que les adresses renvoyées par malloc() et exploitées par les pointeurs sont en fait celles qui sont directement utilisées par ton micro-processeur. Ta RAM, elle, ne cesse jamais d'exister.

    malloc() signifie « Memory Allocation » et sert littéralement à allouer de la mémoire, c'est-à-dire que cette fonction ne fait que tenir un registre des zones réclamées, et te trouve une place officiellement libre quand tu lui en demandes une. Lorsque tu la libères, cette zone est à nouveau déclarée libre, mais elle reste complètement en l'état tant que personne n'écrit dedans à la place. Il est donc normal d'y retrouver les dernières valeurs que tu y as déposées même si rien ne peut le garantir et que tu n'as plus le droit de la lire non plus.

    Si tu fais un free() sur une valeur qui n'a pas été officiellement renvoyée par malloc(), le comportement est indéfini également puisque ça dépend de la façon dont le système choisit de réserver ses plages. Certains systèmes mettent en place un préfixe, c'est-à-dire un bloc de données d'identifications qui se trouve juste avant la zone utile, et donc le pointeur renvoyé. Quand c'est le cas, le système ne peut plus affirmer de façon certaine que ce qu'il y trouve est viable.

    Je. n'importe quel programme peut reprendre ces blocs mémoires. Pourquoi M[1] pointe encore sur les valeurs? juste du hasard? Rien n'a pris la place entre temps?
    Théoriquement oui mais techniquement, il est très peu probable qu'un tout petit bloc de mémoire soit directement rendu au système et immédiatement écrasé, surtout s'il se trouve entre deux autres blocs alloués, en majeure partie à cause du fait que ces autres blocs ne peuvent pas être relocalisés ailleurs pour tasser les trous, car ils sont directement référencés par des pointeurs dans le programme. En outre, chaque processus dispose d'un quantum de temps et le système ne va pas l'interrompre avant la fin s'il n'a pas une raison valable de le faire. Donc, tes boucles consécutives ont le temps d'arriver à terme avant qu'on y réécrive quoi que ce soit.

    e précise que la manière dont j'alloue la mémoire est faite pour avoir une allocation continue des blocs (compatibilité future avec fortran).
    C'est un problème de conception : il faut en réalité allouer ton segment consécutif puis enregistrer son adresse dans un pointeur qui, lui, est déclaré comme pointant un tableau multidimensionnel. Tableau qui, à son tour, est réputé contigu par définition, ce qui est compatible avec l'espace que tu viens d'allouer. Cela donne quelque chose ressemblant à ça :

    Code C : 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
    #include <stdio.h>
     
    int main (void)
    {
        int i,j;
        int tab[2][3] = { {1,2,3}, {4,5,6} };
        int (* ptr)[3];
     
        ptr = tab;
     
        for (i=0;i<2;i++)
        for (j=0;j<3;j++) printf ("[%d][%d]=%d ",i,j,tab[i][j]);
        puts("");
     
        for (i=0;i<2;i++)
        for (j=0;j<3;j++) printf ("[%d][%d]=%d ",i,j,ptr[i][j]);
        puts("");
     
        return 0;
    }

    $ ./programme
    [0][0]=1 [0][1]=2 [0][2]=3 [1][0]=4 [1][1]=5 [1][2]=6 
    [0][0]=1 [0][1]=2 [0][2]=3 [1][0]=4 [1][1]=5 [1][2]=6
    Comme indiqué plus, la plus haute des dimensions importe peu parce que tous les éléments qu'elle contient sont consécutifs. Ainsi, quand on connaît l'adresse du premier élément, on peut en déduire immédiatement celle du suivant et, par récurrence, celle de tous les autres. De fait, on peut passer n'importe quel index à un pointeur même s'il dépasse la zone allouée et, de façon plus surprenante, à un tableau aussi puisqu'il utilise en fait les mêmes mécanismes. Mais si ces éléments sont des « sous-tableaux », alors on est obligé de connaître la taille des dimensions inférieures pour en déduire le pas.

    C'est ce qui va te permettre d'écrire par exemple « int tab [][3] = { {1,2,3}, {4,5,6}, {7,8,9} }; » mais pas « int tab [3][] = …; ».

    Dans le même esprit, donc, ptr est donc un « pointeur sur un tableau de 3 entiers ». En indexant le pointeur, je peux sauter de tableau en tableau, tous consécutifs, et il devient donc compatible avec la déclaration de tab, ce qui me permet de lui associer la valeur de ce dernier.

    Tu peux ensuite, à la place, initialiser ton pointeur avec un malloc() de la bonne longueur.

  5. #5
    Nouveau Candidat au Club
    Homme Profil pro
    Enseignant Chercheur
    Inscrit en
    Janvier 2015
    Messages
    3
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Chili

    Informations professionnelles :
    Activité : Enseignant Chercheur

    Informations forums :
    Inscription : Janvier 2015
    Messages : 3
    Points : 1
    Points
    1
    Par défaut
    Merci beaucoup pour ta réponse,

    C'est beaucoup plus clair! Pour la fin je ne suis pas sur d'avoir tout compris, surtout que je ne peut pas déclarer mon tableau ainsi, dans les fait c'est une matrice nxm ou n et m sont grand et les valeurs que je rentre sont données par d'autre routine. En fait certaines d'entre elles utilises la notation en C avec M[i][j] alors que d'autres viennent du fortran (librairie lapack) et demande un unique pointeur vers le début de la matrice dont les coefficients sont rangés dans un certain autre en mémoire Mais maintenant je crois que c'est clair!

    Encore merci.

  6. #6
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 368
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 368
    Points : 23 622
    Points
    23 622
    Par défaut
    Citation Envoyé par roukinator Voir le message
    C'est beaucoup plus clair! Pour la fin je ne suis pas sur d'avoir tout compris, surtout que je ne peut pas déclarer mon tableau ainsi, dans les fait c'est une matrice nxm ou n et m sont grand et les valeurs que je rentre sont données par d'autre routine. En fait certaines d'entre elles utilises la notation en C avec M[i][j] alors que d'autres viennent du fortran (librairie lapack) et demande un unique pointeur vers le début de la matrice dont les coefficients sont rangés dans un certain autre en mémoire.
    Si c'est juste une question d'ordre, tu peux te permettre de les passer suivant celui de ton choix pour les adapter à ce que tu as en mémoire : s'il faut écrire M[j][k][i] plutôt que M[i][j][k], ce n'est pas un problème. Ce qui est important, c'est que la zone mémoire soit bien en un seul morceau et que les dimensions soient connues.

    Ce que je voulais dire, c'est que si tu veux pouvoir indexer des données organisées de cette manière en mémoire directement avec un pointeur, et sachant que le C organise ses tableaux de la même façon, alors il faut lui expliquer que ce qui est pointé par le pointeur est un tableau. Une fois ceci établi, on peut l'indexer sans difficulté.

    Si tu utilises des pointeurs ordinaires mais chaînés, tu dois non seulement faire plusieurs allocations mais également penser à toutes les libérer, éventuellement dans le bon ordre, et non seulement ces plages ne seront pas consécutives, mais elles n'auront même pas forcément besoin d'avoir toutes la même longueur. Il s'agira plus d'une liste de listes que d'un véritable tableau.

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

Discussions similaires

  1. Réponses: 4
    Dernier message: 12/11/2006, 18h49
  2. Réponses: 2
    Dernier message: 26/09/2006, 13h30
  3. dans quels cas les pointeur sont plus rapides ?
    Par 180degrés dans le forum C++
    Réponses: 12
    Dernier message: 20/08/2005, 23h12
  4. Libération de pointeurs dans un std::vector
    Par G dans le forum SL & STL
    Réponses: 17
    Dernier message: 06/04/2005, 22h37
  5. fuite de memoire dans une liste de pointeur sur composant
    Par Nicolos_A dans le forum Composants VCL
    Réponses: 2
    Dernier message: 16/12/2004, 08h46

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