Je crée ce poste car cela fait un bon moment que je galère à cause d'un problème de realloc :(
Je code un jeu de ferme en gros, et je veux pouvoir ajouter des champs, et donc augmenter la taille du tableau dynamique de structure qui contient chacun des éléments de chaque champ.
J'ai recodé rapidement et commenté un programme simplifié de la situation, le voici :
#include <stdio.h>
#include <stdlib.h>
typedef struct ParcelleChamp ParcelleChamp;
struct ParcelleChamp
{
int laboure;
double var1,var2;
};
void realloc_s (void **ptr, size_t taille) //fonction realloc sécurisée
{
void *ptr_realloc = realloc(*ptr, taille);
if (ptr_realloc != NULL)
*ptr = ptr_realloc;
else
printf("FAIL");
}
void ajouterChamp(ParcelleChamp **mesChamps,int *nbChamps)
{
(*nbChamps)+=1; //on augmente le nb de champs
if(*nbChamps==1)
{
printf("allocation ... ");
*mesChamps = malloc(sizeof(ParcelleChamp)); //un malloc la premiere fois
}
else
{
printf("reallocation ... "); //et après on se sert de realloc pour augmenter le nb de champs
realloc_s((void**)(*mesChamps),(*nbChamps)*sizeof(ParcelleChamp));
}
if(*mesChamps==NULL)
printf("************ECHEC************\n");
else
printf("reussie !!\n");
mesChamps[(*nbChamps)-1]->laboure=0; //on initialise la variable "labouré"
printf(" _ %d",mesChamps[(*nbChamps)-1]->laboure);
}
void realloc_s (void **ptr, size_t taille);
void ajouterChamp(ParcelleChamp **mesChamps,int *nbChamps);
int main()
{
int nbChamps = 0;
ParcelleChamp *mesChamps = NULL; //un pointeur pour créer un tableau dynamique
ajouterChamp(&mesChamps,&nbChamps); //on ajoute un premier champ : malloc
printf("Champ 0 : %d\n",mesChamps[0].laboure);
ajouterChamp(&mesChamps,&nbChamps); //on en ajoute un deuxième : le prog plante lors de l'initialisation de "laboure" !
printf("Champ 1 : %d\n",mesChamps[1].laboure);
free(mesChamps);
return 0;
}
Je ne sais pas pour quelle raison, mais le programme suivant plante dès que j'essaie de toucher de n'importe quelle manière à la case [1] du tableau après le realloc, donc après que la mémoire qui lui est prévue lui soit allouée ...
Si jamais quelque chose n'est pas clair, je peux vous l'expliquer sans problème
Merci beaucoup d'avance !
Un jeune geek
EDIT 1 : le pire, c'est que si je fais ça dans le main, ça compile et c'est bien initialisé ...
int main()
{
int nbChamps = 0;
ParcelleChamp *mesChamps; //un pointeur pour créer un tableau dynamique
ajouterChamp(&mesChamps,&nbChamps); //on ajoute un premier champ : malloc
printf("Champ 0 : %d\n",mesChamps[0].laboure);
//ajouterChamp(&mesChamps,&nbChamps); //on en ajoute un deuxième : le prog plante lors de l'initialisation de "laboure" !
realloc_s((void**)mesChamps,2*sizeof(ParcelleChamp));
mesChamps[1].laboure=0; //on initialise la variable "labouré"
printf("Champ 1 : %d\n",mesChamps[1].laboure);
free(mesChamps);
return 0;
}
Est-ce que j'initialise mal ma variable ? J'ai déjà essayé d'ailleurs de vérifier si mon realloc était bon, et si je modifie la variable dans le main et que je fais le realloc dans la fonction, ça marche ... Donc le problème vient de la variable mais je comprends pas, j'ai tout testé ...
EDIT 2 : je viens de trouver une solution, mais je ne comprends pas pourquoi cela fonctionne mieux que l'autre ...
void ajouterChamp(ParcelleChamp **mesChamps,int *nbChamps)
{
(*nbChamps)+=1; //on augmente le nb de champs
if(*nbChamps==1)
{
printf("allocation ... ");
*mesChamps = malloc(sizeof(ParcelleChamp)); //un malloc la premiere fois
}
else
{
printf("reallocation ... "); //et après on se sert de realloc pour augmenter le nb de champs
ParcelleChamp *p = realloc(*mesChamps,(*nbChamps) * sizeof(ParcelleChamp));
*mesChamps = p;
p=NULL;
}
if(*mesChamps==NULL)
printf("************ECHEC************\n");
else
printf("reussie !!");
(*mesChamps)[(*nbChamps)-1].laboure=0; //on initialise la variable "labouré"
printf(" _ %d",(*mesChamps)[(*nbChamps)-1].laboure);
}
Voici la fonction ajouterChamps fonctionnelle. J'ai changé deux choses :
le realloc se fait directement dedans et non avec la fonction que j'avais trouvé, mais je ne vois pas la différence ...
l'affectation des valeurs à la fin, j'ai mis (*mesChamps)[blabla].blabla au lieu de mesChamps[blabla]->blabla ici, je ne vois toujours pas la différence ... il me semblait que c'était la même chose ...
Et bien sûr, si je ne change pas l'un des deux, le programme plante. Sauriez vous pourquoi ?
Elle est âchement sécurisée ta fonction realloc_s(), elle utilise realloc() qui se charge d'allouer proprement un pointeur, et la première chose que tu fais c'est ce libérer ce bloc. Ta fonction retourne donc un magnifique bloc non alloué! Supprime vite cette ligne 20.
Et ces deux prototypes sont totalement identiques :
void ajouterChamp(ParcelleChamp **mesChamps ,int *nbChamps); // pointeur de pointeur(s)
void ajouterChamp(ParcelleChamp *mesChamps[],int *nbChamps); // tableau de pointeurs, reçu comme pointeur de pointeur(s)
// mais pointeur sur un tableau de structures de taille non connue n'est pas possible
void ajouterChamp(ParcelleChamp (*mesChamps)[],int *nbChamps);
Pour le tableau extensible, voir ici http://www.labri.fr/perso/billaud/travaux/Pointeurs/pointeurs.html#une-application-de-lallocation-dynamique-un-tableau-extensible
Ce qu'on trimballe dans les fonctions, c'est une structure qui contient
- un pointeur sur une zone allouée dyunamiquement
- le nombre total d'éléments que la zone pourrait accueilir (capacité)
Ce n'est pas une bonne stratégie que de réallouer à "capacité + 1" à chaque ajout. Il est préférable de réserver plus grand, et de doubler la capacité quand la limite est atteinte.
Pourquoi ? avec la première stratégie il y aura 1 copie au second ajout, 2 copies au 3 ieme etc. Donc de l'ordre de N^2 copies pour N élements, donc un coût moyen de l'ordre de N. Plus on rajoute des éléments, plus c'est lent.
Si on double, il y aura une copie puis 2 puis 4 puis 8 .... 1+2+4+.... + ... (jusquà la première puissance de 2 supérieure à N) c'est de l'ordre de N. Cout moyen amorti de l'ordre de 1.
- Edité par michelbillaud 3 février 2019 à 17:03:24
Dalfab merci pour tes explications, j'ai modifié mon code en conséquence mais ça plante toujours ... je ne comprends pas :/
Michelbillaud, merci pour le lien et merci du conseil, c'est vrai que c'est certainement mieux de mettre un plafond avec par exemple [64] et si jamais j'ai besoin de plus de place, passer à [128] avec un realloc au lieu d'en faire pleins à la suite ...
Cependant, comment feriez vous une telle réallocation sans realloc ? il n'y a pas la réponse à l'exercice dans votre site (dans le 7.5).
Faut noter qu'on y perd, parce qu'avec un peu de chance, le bloc qu'on veut réallouer peut être suivi d'un bloc libre assez grand pour qu'on puisse étendre en restant sur place, ce qui dispense realloc :
de recopier l'ancien dans le nouveau
d'avoir les deux présents en mémoire, le temps de faire la copie
Back to the problem :
le pointeur non initialisé ligne 53, ça sent vraiment pas bon.
ParcelleChamp *mesChamps; //un pointeur pour créer un tableau dynamique
- Edité par michelbillaud 3 février 2019 à 19:43:15
Maintenant qu'on sait où, il faut voir ce que contiennent les variables à ce moment la. Le pointeur et l'indice.
EDIT : les choses se passent beaucoup mieux en remplaçant le yoyotage au début d'ajouterChamp par un realloc tout simple
void ajouterChamp(ParcelleChamp **mesChamps, int *nbChamps) {
(*nbChamps) += 1; //on augmente le nb de champs
/* POUBELLE
if (*nbChamps == 1) {
printf("allocation ... ");
*mesChamps = malloc(sizeof (ParcelleChamp)); //un malloc la premiere fois
} else {
printf("reallocation ... "); //et après on se sert de realloc pour augmenter le nb de champs
realloc_s((void**) (*mesChamps), (*nbChamps) * sizeof (ParcelleChamp));
}
*/
*mesChamps = realloc(*mesChamps, (*nbChamps) * sizeof(ParcelleChamp));
Tu peux aussi te simplifier énormément la vie avec des variables intermédiaires
Le transtypage, faut faire attention.... Ca peut servir à planquer les problèmes sous le tapis, mais ça les règle pas.
---
Un truc qui fout dedans les débutants aussi, c'est de donner le même nom à une donnée dans un bout du programme (par exemple l'entier nbChamps dans le main), et à un pointeur sur cette donnée ailleurs.
Après on s'étonne de pas s'y retrouver.... Surtout avec le passage en paramètre d'une adresse de pointeur, pour mesChamps. Conseil, l'appeller plutot adr_ptr_champs. Comme ça on ne perd pas de vue que c'est l'adresse d'un pointeur vers les champs, dans la fonction.
Je ne plaide pas pour la notation hongroise, mais pour le fait que les noms de variables doivent refléter leur _nature_ (pas leur type).
- Edité par michelbillaud 4 février 2019 à 12:18:03
Quand quelqu'un dit "j'ai essayé", je demande à voir le code. Histoire qu'on parle de la même chose.
Parce que moi, avec mes modifs, ça tournait. C'est con, j'ai testé dans le /tmp de mon poste au boulot, et si il a rebooté, ça n'y sera plus demain.
Le but du truc c'est d'avoir une variable qui contient l'adresse de la dernière parcelle du tableau, et comme j'ai beaucoup d'imagination je l'appelle adrDerniereParcelle.
Ce qui est ensuite plus facile à utiliser qu'une expression à rallonge contenant des crochets et deux indirections.
-- analyse de ton problème avec realloc_s :
Dans la fonction ajouterChamp, mesChamps est l'adresse d'un pointeur sur une structure (le premier champ du tableau)
tu fais l'appel de realloc_s avec *mesChamps, qui est donc le _contenu_ du pointeur vers le premier champ. C'est à dire l'adresse de ce premier champ.
ta fonction realloc_s s'attend à trouver comme paramètre une adresse de pointeur. En fait c'est l'adresse du premier champ (ou NULL si il n'y en a pas)
le malloc fait une allocation, et met le résultat (l'adresse de la zone allouée) on ne sait où.
en conséquence, une catastrophe ne tarde pas à se produire.
- Edité par michelbillaud 4 février 2019 à 22:02:14
Désolé du retard :/ Après de multiples tests, je me suis rendu compte que ce que j'avais fait sur vos conseils était juste et que ça fonctionnait ! voici la fonction finale :
Donc si j'ai bien compris, on modifie la nouvelle case du tableau grâce à une variable intermédiaire, en donnant à cette variable intermédiaire l'adresse de la case du tableau à ajouter ... ?
Je crois que c'est un peu flou pour moi, je ne comprends pas pourquoi on fait
* Pourquoi on fait : (*mesChamps) + (*nbChamps) - 1 ?
Réponse courte : parce que c'est plus court à écrire.
Réponse longue : ça met à profit l' "arithmétique des pointeurs", une bizarrerie de C.
Si tu as un tableau t et un indice n, l'adresse de t[n] peut s'écrire
& (t[n]) qui est conceptuellement plus simple
ou
t + n; qui est plus facile à écrire
Ca se justife par le fait que t, étant un tableau, se "dégrade" (decay) en pointeur, à qui on peut ajouter un entier pour pointer n éléments plus loin.
Donc (*mesChamps) + (*nbChamps) - 1 , ou encore plus court *mesChamps + *nbChamps - 1, c'est la même chose que &((*mesChamps)[(*nbChamps)-1])
si tu remplace t par *mesChamps, et n par (*nbchamps)-1. Mais si tu aimes taper plein de signes de ponctuation, de gustibus.
* Pas la peine de séparer le premier cas.
Un realloc, quand on lui donne un pointeur NULL, il fait un malloc.
The realloc() function changes the size of the memory block pointed to
by ptr to size bytes. The contents will be unchanged in the range from
the start of the region up to the minimum of the old and new sizes. If
the new size is larger than the old size, the added memory will not be
initialized. If ptr is NULL, then the call is equivalent to mal‐
loc(size), for all values of size; if size is equal to zero, and ptr is
not NULL, then the call is equivalent to free(ptr).
- Edité par michelbillaud 9 février 2019 à 18:37:14
en fait, j'ai un problème de nouveau. J'ai donc intégré cette même fonction dans mon vrai programme et l'affectation des valeurs nouvelles dans le tableau fonctionne, mais dès que mon programme atteint la ligne ci-dessous, il plante.
Process returned 3 (0x3) execution time : 0.138 s
Press any key to continue.
texte étant défini comme ceci :
SDL_Surface *texte=NULL;
J'ai vu plusieurs sujets disant que c'était à cause de la police, qu'il fallait ouvrir "angelina.ttf.TTF" mais cela ne fonctionne toujours pas ...
J'ai le sentiment que c'est un problème d'adresses ; en fait, le programme se lance 1 fois sur 10 avec des bugs immondes dus au changement des valeurs de mon tableau précédemment initialisé ...
J'ai fait quelques tests, et voici le résultat :
avant et après la création des nouveaux champs, j'ai fait afficher quelques variables de chaque champ avec ceci :
Fais voir un programme MINIMAL qui démontre clairement que tu utilises correctement ton RenderText et sa police.
Problème de realloc de structure dans une fonction
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
En recherche d'emploi.