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 :

Double free dans un tableau dynamique de pointeurs sur des structures


Sujet :

C

  1. #1
    Nouveau Candidat au Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Janvier 2017
    Messages
    1
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2017
    Messages : 1
    Points : 1
    Points
    1
    Par défaut Double free dans un tableau dynamique de pointeurs sur des structures
    Bonjour !

    Voici mon code :

    adherents.txt
    1 MICHAUD Apolline apolline.m@gmail.com 02 03 2016 2
    2 DODIER Marcelle marcelle.d@gmail.com 24 04 2016 1
    3 VENNETIER Violaine violaine.v@gmail.com 01 01 2016 0
    4 CHARBONNEAU Dominique dominique.c@gmail.com 12 12 2016 3
    5 MARCHAND Eustache eustache.m@gmail.com 12 04 2016 2
    projet.c
    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
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    #include "projet.h"
     
    void adherents_global()
    {
    	Adherent **tadherents, a;
    	int nbAdherents=0, i;
     
    	tadherents=(Adherent**)malloc(sizeof(Adherent*));
    	if(tadherents == NULL)
    	{
    		printf("Erreur : Impossible d'allouer de la mémoire.\n");
    		exit(1);
    	}
     
    	tadherents=adherents_chargement(tadherents, &nbAdherents);
     
    	for(i=0;i<nbAdherents;i++)
    		adherents_affichage(tadherents[i]);
     
    	/* --- SUPPRESSION --- */
    	int tmp;
    	printf("\nEntrez l'ID de l'adhérent à supprimer : ");
    	scanf("%d",&tmp);
    	tadherents=adherents_supprimer(tadherents,&nbAdherents,tmp);
     
    	for(i=0;i<nbAdherents;i++)
    		adherents_affichage(tadherents[i]);
    	/* --- /SUPPRESSION --- */
     
    	/* +++ AJOUT +++ */
    	printf("\nEntrez l'adresse mail du nouvel adherent : ");
    	scanf("%s%*c", a.mailAd);
    	while(adherents_emailCheck(a.mailAd) != 0)
    	{
    		printf("\nVeuillez entrer une adresse e-mail valide.");
    		printf("\nEntrez l'adresse mail de l'adherent : ");
    		scanf("%s%*c",a.mailAd);
    	}
    	i=adherents_emailExists(tadherents, nbAdherents, a);
    	if(i != -1)
    	{
    		printf("Cette adresse email est déjà utilisée.\n");
    		printf("Elle appartient à %s %s.\n",tadherents[i]->prenomAd, tadherents[i]->nomAd);
    	}
    	else
    	{
    		printf("\nEntrez le nom complet de l'adherent : ");
    		scanf("%s%s%*c",a.nomAd, a.prenomAd);
    		//uppercase, choix date ajd OU date manuelle
    		printf("\nEntrez la date d'inscription (jj/mm/aaaa) : ");
    		scanf("%d/%d/%d",&a.dateDAbo.jour,&a.dateDAbo.mois,&a.dateDAbo.annee);
    		while(adherents_dateCheck(a.dateDAbo.jour,a.dateDAbo.mois,a.dateDAbo.annee) != 0)
    		{
    			printf("Cette date n'est pas valide.\n");
    			printf("\nEntrez la date d'inscription (jj/mm/aaaa) : ");
    			scanf("%d/%d/%d",&a.dateDAbo.jour,&a.dateDAbo.mois,&a.dateDAbo.annee);
    		}
    		tadherents=adherents_creer(tadherents,&nbAdherents,a);
    	}
     
    	for(i=0;i<nbAdherents;i++)
    		adherents_affichage(tadherents[i]);
    	/* +++ /AJOUT +++ */
     
    	printf("\n");
     
    	for(i=0;i<nbAdherents;i++)
    	{
    		printf("Je libère la case %d.\n",i);
    		free(tadherents[i]);
    	}
    	free(tadherents);
    }
     
    Adherent ** adherents_chargement(Adherent **tab, int *n)
    {
    	FILE *fAdh;
    	Adherent a;
     
    	fAdh=fopen("adherents.txt","r");
    	if(fAdh == NULL)
    	{
    		printf("Erreur : Impossible d'ouvrir le fichier en lecture.\n");
    		exit(1);
    	}
     
    	a=adherents_lecture(fAdh);
    	while(!feof(fAdh))
    	{
    		tab=(Adherent**)realloc(tab, (*n+1) * sizeof(Adherent*));
    		if(tab == NULL)
    		{
    			printf("Erreur : Impossible d'allouer de la mémoire.\n");
    			exit(1);
    		}
    		tab[*n]=(Adherent *)malloc(sizeof(*tab[*n]));
    		*tab[*n]=a;
    		(*n)++;
    		a=adherents_lecture(fAdh);
    	}
    	fclose(fAdh);
    	return tab;
    }
     
    Adherent adherents_lecture(FILE *fp)
    {
    	Adherent a;
    	fscanf(fp,"%d%s%s%s%d%d%d%d",&a.identifiant,a.nomAd,a.prenomAd,a.mailAd,&a.dateDAbo.jour,&a.dateDAbo.mois,&a.dateDAbo.annee,&a.nbJeuEmp);
    	return a;
    }
     
    void adherents_affichage(Adherent *a)
    {
    	printf("%d %s\t%s\t%s\t%d/%d/%d\t%d\n",a->identifiant,a->nomAd,a->prenomAd,a->mailAd,a->dateDAbo.jour,a->dateDAbo.mois,a->dateDAbo.annee,a->nbJeuEmp);
    }
     
    int adherents_rechercheId(Adherent **tab, int n, int id)
    {
    	int i;
    	for(i=0; i <= n-1; i++)
    		if(id == tab[i]->identifiant)
    			return i;
    	return -1;
    }
     
    Adherent ** adherents_supprimer(Adherent **tab, int *n, int id)
    {
    	int i=adherents_rechercheId(tab, *n, id);
    	if(i != -1)
    	{
    		printf("L'identifiant correspond a %s %s.\n",tab[i]->prenomAd, tab[i]->nomAd);
    		printf("\nJe libère la case %d !\n",i);
    		free(tab[i]);
    		for(i; i<*n-1; i++)
    			tab[i]=tab[i+1];
    		printf("L'adhérent a été supprimé.\n\n");
    		(*n)--;
    	}
    	else
    		printf("\nCet identifiant n'existe pas.\n\n");
    	return tab;
    }
     
    Adherent ** adherents_creer(Adherent **tab, int *n, Adherent a)
    {
    	Adherent **aux;
    	int id, i, j;
     
    	id=adherents_idNouveau(tab, *n);
    	a.identifiant=id;
    	a.nbJeuEmp=0;
     
    	aux=(Adherent**)realloc(tab,(*n+1)*sizeof(Adherent*));
    	if(aux == NULL)
    	{
    		printf("Erreur : Impossible d'allouer de la mémoire.\n");
    		exit(1);
    	}
     
    	tab=aux;
    	printf("\nJ'alloue à la case %d.\n",id-1);
    	tab[id-1]=(Adherent*)malloc(sizeof(Adherent));
    	*tab[id-1]=a;
    	(*n)++;
     
    	return tab;
    }
     
    int adherents_emailCheck(char e[])
    {
    	if(*e =='\0')
    		return -1;
    	if(*e =='@')
    		return 0;
    	return adherents_emailCheck(e+1);
    }
     
    int adherents_emailExists(Adherent **tab, int n, Adherent a)
    {
    	int i;
    	for(i=0; i<n; i++)
    		if(strcmp(a.mailAd,tab[i]->mailAd)==0)
    			return i;
    	return -1;
    }
     
    int adherents_dateCheck(int j, int m, int a)
    {
    	if(m == 2 && j > 29)
    		return -1;
    	if(j < 1 || j > 31 || m < 1 || m > 12 || a < 1900 || a > 2099)
    		return -1;
    	return 0;
    }
     
    int adherents_idNouveau(Adherent **tab, int n)
    {
    	int i;
    	for(i=1;i<=n;i++) 
    		if(i!=tab[i-1]->identifiant)
    			return i;
    	return n+1;
    }
    projet.h
    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 <stdio.h>
    #include <stdlib.h>
    #include <string.h>
     
    typedef struct
    {
    	int jour;
    	int mois;
    	int annee;
    	int heure;
    	int minute;
    } Date;
     
    typedef struct
    {
    	int identifiant;
    	char nomAd[15];
    	char prenomAd[15];
    	char mailAd[40];
    	Date dateDAbo;
    	int nbJeuEmp;
    } Adherent;
     
    void adherents_global();
    Adherent ** adherents_chargement(Adherent **tab, int *n);
    Adherent adherents_lecture(FILE *flot);
    void adherents_affichage(Adherent *a);
    int adherents_rechercheId(Adherent **tab, int n, int id);
    Adherent ** adherents_supprimer(Adherent **tab, int *n, int id);
    Adherent ** adherents_creer(Adherent **tab, int *n, Adherent a);
    int adherents_emailCheck(char e[]);
    int adherents_dateCheck(int j, int m, int a);
    int adherents_emailExists(Adherent **tab, int n, Adherent a);
    int adherents_idNouveau(Adherent **tab, int n);
    testprojet.c
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    #include "projet.h"
     
    int main(void)
    {
    	adherents_global();
    	return 0;
    }
    Je suis en première année de DUT Info, et je réalise un projet de C.
    Il s'agit de la gestion d'une ludothèque, où mon programme doit être capable de gérer une liste d'adhérents (ajouter, supprimer, etc).
    J'ai choisi de gérer mes adhérents avec un tableau dynamique de pointeurs sur des structures "Adherent".
    Mon programme charge un fichier texte, remplit le tableau avec les informations du fichier, j'effectue mes opérations puis je sauvegarde.

    J'ai donc une fonction pour supprimer, et une autre pour ajouter un adhérent. Ces deux fonctions marchent parfaitement... mais individuellement. Je m'explique.
    Quand je vais vouloir supprimer un adhérent, puis en ajouter un nouveau, notre programme est censé récupérer le "trou" créé par la suppression pour le re-remplir.
    Dans l'exemple que je vous donne, ça marche bien si j'essaie avec les adhérents d'ID 4 et 5. Mais si j'essaie avec 1, 2 et 3, il m'indique un double free() à la fin du programme, d'une case qui n'est même pas concernée !
    Je vous invite sérieusement à tester mon programme pour comprendre le problème rapidement, c'est assez difficile à expliquer.

    J'ai testé mon programme avec valgrind, voici ce qu'il m'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
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    ==2107== Memcheck, a memory error detector
    ==2107== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
    ==2107== Using Valgrind-3.10.0 and LibVEX; rerun with -h for copyright info
    ==2107== Command: ./test --leak-check=full --track-origins=yes -v
    ==2107== 
    1 MICHAUD	Apolline	apolline.m@gmail.com	2/3/2016	2
    2 DODIER	Marcelle	marcelle.d@gmail.com	24/4/2016	1
    3 VENNETIER	Violaine	violaine.v@gmail.com	1/1/2016	0
    4 CHARBONNEAU	Dominique	dominique.c@gmail.com	12/12/2016	3
    5 MARCHAND	Eustache	eustache.m@gmail.com	12/4/2016	2
     
    Entrez l'ID de l'adhérent à supprimer : 2
    L'identifiant correspond a Marcelle DODIER.
     
    Je libère la case 1 !
    L'adhérent a été supprimé.
     
    1 MICHAUD	Apolline	apolline.m@gmail.com	2/3/2016	2
    3 VENNETIER	Violaine	violaine.v@gmail.com	1/1/2016	0
    4 CHARBONNEAU	Dominique	dominique.c@gmail.com	12/12/2016	3
    5 MARCHAND	Eustache	eustache.m@gmail.com	12/4/2016	2
     
    Entrez l'adresse mail du nouvel adherent : test@test.fr
     
    Entrez le nom complet de l'adherent : NOUVEAU Monsieur
     
    Entrez la date d'inscription (jj/mm/aaaa) : 01/02/2013
     
    J'alloue à la case 1.
    1 MICHAUD	Apolline	apolline.m@gmail.com	2/3/2016	2
    2 NOUVEAU	Monsieur	test@test.fr	1/2/2013	0
    4 CHARBONNEAU	Dominique	dominique.c@gmail.com	12/12/2016	3
    5 MARCHAND	Eustache	eustache.m@gmail.com	12/4/2016	2
    5 MARCHAND	Eustache	eustache.m@gmail.com	12/4/2016	2
     
    Je libère la case 0.
    Je libère la case 1.
    Je libère la case 2.
    Je libère la case 3.
    Je libère la case 4.
    ==2107== Invalid free() / delete / delete[] / realloc()
    ==2107==    at 0x4C29E90: free (vg_replace_malloc.c:473)
    ==2107==    by 0x400CB1: adherents_global (in <...>/test)
    ==2107==    by 0x401472: main (in <...>/test)
    ==2107==  Address 0x51e07a0 is 0 bytes inside a block of size 100 free'd
    ==2107==    at 0x4C29E90: free (vg_replace_malloc.c:473)
    ==2107==    by 0x400CB1: adherents_global (in <...>/test)
    ==2107==    by 0x401472: main (in <...>/test)
    ==2107== 
    ==2107== 
    ==2107== HEAP SUMMARY:
    ==2107==     in use at exit: 100 bytes in 1 blocks
    ==2107==   total heap usage: 14 allocs, 14 frees, 1,336 bytes allocated
    ==2107== 
    ==2107== LEAK SUMMARY:
    ==2107==    definitely lost: 100 bytes in 1 blocks
    ==2107==    indirectly lost: 0 bytes in 0 blocks
    ==2107==      possibly lost: 0 bytes in 0 blocks
    ==2107==    still reachable: 0 bytes in 0 blocks
    ==2107==         suppressed: 0 bytes in 0 blocks
    ==2107== Rerun with --leak-check=full to see details of leaked memory
    ==2107== 
    ==2107== For counts of detected and suppressed errors, rerun with: -v
    ==2107== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
    Pour tenter de débugger j'ai ajouté des printf() avant chaque free() pour voir à quel moment la case en question est libérée, mais à aucun moment la case en question ne s'affiche.

    Par ailleurs, j'ai conscience qu'il y a des trucs dans mon code qui vous feront tilt, comme le while(!feof) pour lire dans mon fichier, ou les "(Adherent*) devant les malloc/realloc.
    Ce sont les techniques que mon prof nous a apprises, et même si c'est pas très correct de faire comme ça (il est de la vieille école, comme on dit ^^), je fais avant tout mon projet pour la note qui m'y sera attribuée, alors tant pis...

    Je vous serais VRAIMENT reconnaissant de m'aider, ce n'est pas le premier problème casse-tête comme ça que l'on rencontre, et ça commence à nous faire perdre un temps considérable...
    Merci d'avance !

  2. #2
    Expert confirmé
    Inscrit en
    Mars 2005
    Messages
    1 431
    Détails du profil
    Informations forums :
    Inscription : Mars 2005
    Messages : 1 431
    Points : 4 182
    Points
    4 182
    Par défaut
    Personnellement, je ne rapatrie rien qui ne provienne d'une source que je peux identifier. Poste du code ici-même ou à la limite sur un dépôt public (GitLab, Github..).

    Montre-nous déjà les définitions de types ainsi que les détails de gestion mémoire des éléments et du conteneur.

  3. #3
    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 679
    Points
    13 679
    Billets dans le blog
    1
    Par défaut
    J'ai rapatrié ici les 4 fichiers qui étaient dans un zip hébergé sur Mega. A l'avenir, merci de mettre directement tes fichiers ici de cette manière, c'est plus facile pour les gens qui souhaitent regarder ton code et t'aider

  4. #4
    Membre averti
    Homme Profil pro
    très occupé
    Inscrit en
    Juillet 2014
    Messages
    137
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : très occupé

    Informations forums :
    Inscription : Juillet 2014
    Messages : 137
    Points : 411
    Points
    411
    Par défaut
    Dans ton code, chaque entrée de ton tableau est un pointeur sur un pointeur sur struct.

    Tu alloues la mémoire pour ton pointeur sur ton pointeur, puis à partir de cet emplacement mémoire (1), tu mets des pointeurs sur struct pour stocker tes infos qui pointent donc sur un autre emplacement mémoire (2).

    Si tu libères la mémoire pointée par 1, tu perds le moyen de libérer la mémoire pointée par 2.

    Il faut donc libérer d'abord 2, puis 1.

    En outre, la libération dans les deux niveaux doit correspondre aux allocations. Sur le premier niveau, tu fais un (seul) malloc avec des realloc pour étendre la mémoire. Donc, sur ce niveau, tu dois faire un (seul) free.

    Cependant, tu te compliques inutilement la vie à mon sens. Il suffit, pour faire ce que tu veux, de disposer d'un tableau avec des pointeurs sur struct, et c'est tout.

  5. #5
    Expert confirmé
    Inscrit en
    Mars 2005
    Messages
    1 431
    Détails du profil
    Informations forums :
    Inscription : Mars 2005
    Messages : 1 431
    Points : 4 182
    Points
    4 182
    Par défaut
    Merci Bktero.

    Je suis d'accord avec l'intervenant qui me précède pour dire que l'implémentation en elle-même est inutilement alambiquée. Peut-être est-ce l'énoncé qui veut cela ? Cf. les mises en garde de l'OP à propos des mauvaises pratiques présentes.

    En revanche je ne suis pas du même avis concernant le diagnostic : pour moi la cause première du comportement indéterminé du programme est l'assymétrie des fonctions adherents_supprimer et adherents_creer. Il y a une opération en particulier qui est réalisée sur le conteneur au sein de la première mais qui n'est pas reflétée dans la seconde. Ce manquement entraîne effectivement une corruption mémoire. Je n'en dis pas plus, ça devrait être amplement suffisant pour mettre le doigt sur ce qui ne va pas.

    Cela dit, je trouve le code « pas si dégueu » dans le sens où il donne tout de même l'impression que l'OP a conscience de ce qu'il fait (de plus l'utilisation de valgrind est un bon réflexe).

  6. #6
    Membre averti
    Homme Profil pro
    très occupé
    Inscrit en
    Juillet 2014
    Messages
    137
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : très occupé

    Informations forums :
    Inscription : Juillet 2014
    Messages : 137
    Points : 411
    Points
    411
    Par défaut
    Salut Matt_Houston,

    Tu as raison, les allocations semblent bien correspondre aux libérations.

    Cependant, est-ce que le problème ne viendrait pas plutôt de adherents_idNouveau(), appelé dans adherents_creer(), qui renvoie 2 sur le test (id-1 affiche 1 sur le test), et comme id-1 est utilisé comme index de stockage pour le pointeur, le programme écrase un pointeur sur de la mémoire allouée, et n'ajoute pas le pointeur nouvellement acquis à la fin de la liste.

    sur le test effectué adherents_idNouveau() devrait renvoyer 5, pour éviter de corrompre la mémoire,...

    en tout cas, le résultat de adherents_idNouveau() ne peut pas être utiilsé comme index de tab, il faudrait simplement partir de *n.

Discussions similaires

  1. Réponses: 3
    Dernier message: 23/03/2015, 11h12
  2. Coordonnées d'un double clic dans un tableau croisé dynamique
    Par tantrika dans le forum Macros et VBA Excel
    Réponses: 1
    Dernier message: 27/05/2012, 16h24
  3. [XL-2007] Détection d'un double-clic dans un tableau croisé dynamique
    Par damsmut dans le forum Macros et VBA Excel
    Réponses: 3
    Dernier message: 22/03/2012, 13h47
  4. Double tableau dynamique de pointeurs de struct
    Par dedibox26 dans le forum Débuter
    Réponses: 2
    Dernier message: 27/04/2010, 15h24
  5. Tableau dynamique de pointeurs sur const char*
    Par Le Mérovingien dans le forum Débuter
    Réponses: 6
    Dernier message: 05/06/2008, 14h23

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