Si j'appelle la fonction afficherListe(), j'ai une belle erreur de segmentation .
Elle m'affiche un nombre complètement aléatoire (identique à celui que j'avais dans le résultat au-dessus) et l'adresse du premier élément de la liste chaînée (identique aussi).
Donc on peut en déduire que tout est bien supprimé ?
Tous les éléments sont bien détruit, mais pas la liste créé avec malloc ligne 19. Donc on devrait plutôt dire que ta fonction vide la liste au lieu de la détruire. Si tu veux la détruire, il faudra aussi un free qui correspond à ce malloc.
PS : Est-ce bien utile de créer un élément à 0 lors de l'initialisation ? Tu aurais pu mettre liste->premier à NULL pour signifier qu'il n'y a pas d'élément dans la liste.
EDIT :
Terence01 a écrit:
Si j'appelle la fonction afficherListe(), j'ai une belle erreur de segmentation .
Comme tu l'as vidée, il n'y a pas d'élément à afficher, et liste->premier pointe à un emplacement qui n'est plus valide, d'où l'erreur de segmentation.
Ta fonction de destruction est inutilement compliquée:
void destructionListe(Liste *liste) {
while(liste->premier) {
Element *tmp=liste->premier->suivant;
printf("Suppression de %d\n", liste->premier->nombre);
free(liste->premier);
liste->premier=tmp;
}
}
Et encore, la ligne 4 n'est qu'informative. Ça a l'avantage que liste->premier sera NULL à la fin.
rouIoude a écrit:
PS : Est-ce bien utile de créer un élément à 0 lors de l'initialisation ? Tu aurais pu mettre liste->premier à NULL pour signifier qu'il n'y a pas d'élément dans la liste.
Tu as entièrement raison, c'est le cours de oc qui n'est pas correct.
- Edité par edgarjacobs 23 avril 2024 à 18:38:00
On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent
Ta fonction de destruction est inutilement compliquée:
void destructionListe(Liste *liste) {
while(liste->premier) {
Element *tmp=liste->premier->suivant;
printf("Suppression de %d\n", liste->premier->nombre);
free(liste->premier);
liste->premier=tmp;
}
}
Et encore, la ligne 4 n'est qu'informative. Ça a l'avantage que liste->premier sera NULL à la fin.
rouIoude a écrit:
PS : Est-ce bien utile de créer un élément à 0 lors de l'initialisation ? Tu aurais pu mettre liste->premier à NULL pour signifier qu'il n'y a pas d'élément dans la liste.
Tu as entièrement raison, c'est le cours de oc qui n'est pas correct.
- Edité par edgarjacobs il y a environ 15 heures
Bonjour,
Merci pour les réponses,
En effet ta fonction est nettement plus simple et compréhensible !
Je m'obstinais à vouloir créer des éléments pour bosser dessus alors que ce n'est pas utile.
Je me rends compte que ça m'obligeait aussi à rajouter la ligne "liste->premier=elementToDelete;" pour avoir ma liste à NULL à nouveau.
ça veut dire que mon premier élément dans liste ne changerait jamais et aurait gardé l'adresse de départ ?
Tu utilises dans ton code cette instruction, je ne connaissais pas ceci => liste->premier->suivant;
On ne le voit pas dans le cours d'OpenClassroom, ici ça veut dire qu'on pointe sur l'adresse suivante du premier élément ?
Maintenant que liste->premier = NULL, plus besoin de faire un free(liste->premier) à la toute fin c'est ça ?
On considère donc à ce moment que la liste est détruite ? ou juste vidé comme disait rouloude ? Pour moi il reste 1 élément encore même s'il est à NULL.
Est-ce qu'on peut rester dans cette état ou faut-il obligatoirement clôturer cette liste ? (Désolé si les questions sont chiantes, je préfère demander que de le laisser traîner des variables n'importe où)
J'ai corrigé aussi l'initialisation, j'ai mis uniquement liste->premier = NULL.
Je trouvais ça "normal" (bien que cela ne le soit pas) que Element->nombre= 0; dans le code vu qu'on remplit un int, je n'avais pas fait attention que c'était un pointeur.
Pour la fonction afficherListe(), je l'ai modifié aussi, désormais si liste->premier = NULL alors j'affiche que la liste est vide
void afficherListe(Liste *liste)
{
if(liste == NULL)
{
exit(EXIT_FAILURE);
}
if(liste->premier==NULL)
{
printf("La liste est vide\n");
}
Element *actuel = liste->premier;
while (actuel !=NULL)
{
printf("%d, adresse -> ", actuel->nombre);
printf("%p\n", actuel);
actuel = actuel->suivant;
}
printf("NULL\n");
}
On considère donc à ce moment que la liste est détruite ? ou juste vidé comme disait rouloude ? Pour moi il reste 1 élément encore même s'il est à NULL.
Ici, elle est vide, mais pas détruite car le pointeur maListe pointe toujours sur un espace mémoire valide de type Liste (créé avec ton malloc ligne 19) Elle n'a plus d'élément car son champ premier est NULL, mais tu pourrais encore lui rajouter des éléments avec la fonction insertion .
Terence01 a écrit:
Est-ce qu'on peut rester dans cette état ou faut-il obligatoirement clôturer cette liste ? (Désolé si les questions sont chiantes, je préfère demander que de le laisser traîner des variables n'importe où)
Ça c'est un choix, il n'y a aucune obligation, ça dépend de ce que tu veux en faire. Il faut bien sur nommer les fonction correctement ex : detruireListe , viderListe.
Je viens d'essayer de rajouter dans mon main un free(maListe)
Avant de faire ce free(maListe), maListe->premier vaut NULL, après le free, il prend une adresse en mémoire, pourquoi ?
Cela ne suffit pas pour détruire cette liste ? Comment doit-on si prendre ?
Vider la liste ça ok, c'est réglé mais juste pour la curiosité je veux comprendre comment la supprimer purement et simplement (pour éviter les fuites mémoires aussi)
int main(int argc, const char* argv[])
{
int i=0;
Liste *maListe = initialisation();
// printf("Adresse maListe => %p\n", maListe->premier);
insertion(maListe, 35);
insertion(maListe, 42);
insertion(maListe, 69);
insertion(maListe, 13);
insertion(maListe, 21);
insertion(maListe, 27);
printf("Il y a actuellement %d elements dans ma liste\n", maListe->i_nbElements);
printf("Il y a actuellement %d elements dans ma liste selon ma fonction Taille liste\n", TailleListe(maListe));
afficherListe(maListe);
// Destruction de toute la liste
printf("Destruction de la liste\n");
destructionListe(maListe);
printf("Il y a actuellement %d elements dans ma liste selon ma fonction Taille liste\n", TailleListe(maListe));
free(maListe);
printf("adresse => %p\n", maListe->premier);
printf("\n\n");
afficherListe(maListe);
if(maListe->premier==NULL)
{
printf("Ma liste est vide\n");
}
return 0;
}
Autre question aussi, dans ma structure Liste, j'ai rajouté un int i_nbElements pour compter le nombre d'éléments dans la liste.
typedef struct Liste Liste;
struct Liste
{
int i_nbElements;
Element *premier;
};
Lors de l'initialisation dans la fonction initialisation(), j'ai voulu l'initialiser à NULL aussi mais ça me sort un warning
warning: assignment to 'int' from 'void *' makes integer from pointer without a cast
Par contre si je l'initialise avec un 0, aucun warning.
J'avoue que je suis un peu perdu là car pour l'assignement j'utilise "->" donc je pointe bien sur ma structure avec ça. Dans les messages précédents, on me disait d'initialiser avec NULL mais là, le compilateur n'aime pas trop on dirait.
Liste *initialisation()
{
Liste *liste = malloc(sizeof(*liste));
Element *element = malloc(sizeof(*element));
if(liste == NULL || element == NULL)
{
exit(EXIT_FAILURE);
}
// element->nombre=NULL;
// element->suivant=NULL;
liste->premier=NULL;
// initialisation du nombre d'éléments à 0
liste->i_nbElements = NULL;
return liste;
}
Après avoir fait free(maListe) le pointeur maListe n'est plus valide. Tu n'as plus le droit de le déréférencer, c'est à dire d’accéder où il pointe. Donc faire maListe->premier est une erreur qui peut provoquer un comportement indéterminé comme une erreur de segmentation ou autre...
Terence01 a écrit:
Avant de faire ce free(maListe), maListe->premier vaut NULL, après le free, il prend une adresse en mémoire, pourquoi ?
Une fois la mémoire libéré, les valeurs qu'elle contient peuvent être n'importe quoi.
Quand tu as vidé ta liste et que tu as fait le free(maListe) toutela mémoire que tu avais alloué est libéré, on peut considéré que ta liste est détruite.
Le pointeur, lui il existe toujours, mais il n'est plus valide, tu ne doit plus le déréférencer en l'état.
Une solution pour le marquer non valide est de le mettre à NULL après avoir libéré la mémoire sur laquelle il pointait, comme cela tu peut le tester avant de l'utiliser.
Terence01 a écrit:
dans ma structure Liste, j'ai rajouté un int i_nbElements pour compter le nombre d'éléments dans la liste.
Lors de l'initialisation dans la fonction initialisation(), j'ai voulu l'initialiser à NULL aussi mais ça me sort un warning
i_nbElements est un entier de type int et un int ça initialise avec un entier : ici avec 0. NULL c'est pour les pointeurs (Je te l'ai dit plus haut).
[...] je ne connaissais pas ceci => liste->premier->suivant;
[...] ça veut dire qu'on pointe sur l'adresse suivante du premier élément ?
Y a pas de "on" qui pointe.
Il y a une variable liste, qui dans ce contexte est en réalité un pointeur, de type struct Liste *.
(Remarque : C'est un peu confusionnant de nommer "liste" un truc qui contient en fait l'_adresse_ d'une liste. Enfin bref on va faire avec)
Bref, la variable liste est un pointeur, *liste désigne la zone mémoire pointée par liste, qui est - si le pointeur a bien été initialisé etc- un struct liste, qui a un champ (une donnée membre, dans le jargon officiel de C) qui s'appelle premier
On peut le noter de deux façons
(*liste).premier
liste->premier
Pour la première il faut mettre les parenthèses, parce que si on écrit *a.b, ça se lit *(a.b) selon les règles de priorité des opérateurs, c'est à dire que a est une structure, b un de ses champs, qui est un pointeur, et *(a.b) c'est le truc désigné par ce pointeur.
Bref (bis) dans liste->premier->suivant
liste->premier est lui aussi un pointeur (de type struct Element *)
liste->premier->suivant est le champ "suivant" de ce qu'il pointe.
Conseil : Faut faire des dessins, ça évite de se prendre la tête.
Les boites représentent des zones mémoires (octets contigus),
une flèche indique que le contenu d'une boite est l'adresse d'une autre boîte, pour voir laquelle suivez la flèche.
Et on met une croix quand c'est NULL (la constante qui vaut zéro et indique que le pointeur NE DÉSIGNE PAS une zone en mémoire)
PS
PS : une des difficultés de la programmation est de bien choisir les noms de variables, des fonctions etc pour se simplifier la vie ensuite.
Le nombre d'éléments d'une liste, c'est ce qu'on appelle sa taille, alors autant l'appeler comme ça
struct Liste {
int taille;
struct Element *premier;
};
(et normalement c'est jamais négatif, et ça pourrait être de type unsigned int)
---
PS: ce cours est mal rédigé. Quand on voit par exemple
> Il suffit de faire pointer le dernier élément de la liste vers NULL, c'est-à-dire de mettre son pointeur suivant à NULL :
1) Ben non, le dernier élément ne pointe pas vers NULL.
a) parce que le dernier élément n'est pas un pointeur, mais une structure, et une structure ça pointe pas. C'est le champ suivant qui est un pointeur susceptible de pointer sur quelque chose
b) NULL n'est pas une donnée en mémoire, alors on ne peut pas pointer dessus (= contenir son adresse). C'est une valeur spéciale [1] que par convention on utilise pour signaler l'absence d'une adresse normale [2].
2) Ce qu'on veut dire, c'est que le dernier élément de la liste, par définition, n'a pas de successeur. Et pour représenter ça, on a dans le dernier pointeur la valeur spéciale NULL.
Notes:
[1] qui vaut 0, scoop.
[2] alors que l'adresse 0 existe en réalité. Sauf qu'elle se trouve (sur un système moderne) à un endroit de la mémoire où on ne peut en principe pas accéder (protections mémoire etc).
- Edité par michelbillaud 25 avril 2024 à 12:17:52
@rouloude Ok je comprends mieux maintenant, après le free(maListe), je le mets à NULL et je ne le touche plus, c'est mieux.
@edgarjacobs je vois ce que tu veux dire, en fait je me mélangeais les pinceaux au moment de l'initialisation, pour moi dans la fonction initalisation() quand j'utilisais "liste" vu que c'est un pointeur, on ne pouvait pas mettre de valeur à i_nbElement et donc on devait mettre NULL car je considérais du fait que "liste" soit un pointeur tout devait être à NULL mais non.
Si j'ai bien compris, liste sera un pointeur donc on utilisera "->" pour assigner les valeurs à chaque donnée membre et après on remplira ces données membres en fonction de leur type :
int, long, double ou autre = 0
pointeur = NULL
@michelbillaud wouah fallait pas se donner tant de mal , merci pour les schémas et le rappel sur les structures, c'est vrai que c'est plus parlant. je comprends mieux comment accéder à chaque variable/champ
En fait, il n'y a pas aucune raison qui oblige à ce que les "struct Liste" soient allouées dynamiquement, comme c'est fait dans ce cours écrit par un pizzaïolo
Liste *maListe = initialisation();
On pourrait très bien écrire
Liste maListe = LISTE_VIDE;
avec
const Liste LISTE_VIDE = {
.premier = NULL;
};
et là, la variable maListe est de type Liste. Comme son nom l'indique. Pas un pointeur de Liste.
---
Ensuite, pour agir sur cette liste, on passe son adresse aux fonctions inserer etc.
ajouter_au_debut(& maListe, 33);
puisque ça peut modifier la structure (changer le champ premier), et que C ne connaît que le passage par valeur.
Bonus : la fonction qui vide une liste (en libérant tous les maillons)
void vider_liste( Liste *l)
{
while (l->premier != NULL) {
enlever_premier(l); // décomposer le travail...
}
}
va laisse une structure propre, dont le champ premier est à NULL. Alors que sa fonction destruction_liste ne libère pas la structure Liste allouée lors de l'initialisation, et de toutes façons ne remet pas le pointeur maListe à NULL, le pointeur est donc invalide.
Bref, c'est le souk.
---
Les causes probables de cette erreur de conception du cours
le type a décidé que faire comme si on programmait en Java ou en C# (où les variables sont des références)
ça l'emmerde de mettre des &
Dommage, parce que là on programme en C, et on est en plein dans un chapitre où il faut donner une vision claire de ce qu'est une donnée, une adresse, un pointeur, un déreférencement etc. Et si on commence à les planquer sous le tapis, c'est pas comme ça qu'on va apprendre.
En tout cas, si c'est un choix conscient, il aurait fallu l'expliciter.
- Edité par michelbillaud 25 avril 2024 à 16:21:08
J'ai repris ce cours de langage C, je l'avais déjà fait il y a quelques années sans le terminer mais c'est vrai que je l'ai trouvé différent et comme si certaines parties avaient été enlevées. Après je me suis dit autant terminer ce cours pour avoir une première base et ensuite fouiller un peu plus pour des contenus plus fournis.
Après moi j'ai rien contre les pizzaïolos
Juste je reviens sur ce code :
const Liste LISTE_VIDE = {
.premier = NULL;
};
Pas besoin de mettre le mot clé struct ?
et ce "." devant premier, ça signifie quoi ? on ne met pas de type ici ?
J'ai jamais vu la construction d'une structure comme ça.
le compilateur voit que ça ne commence pas par struct (ou union) et va regarder dans la table des typedef qui ont été déclarés.
Exemple pathologique mais parfaitement valide :
struct X { int i; };
typedef char* X;
void test() {
struct X n;
X s;
}
on définit deux types distincts struct X et X. Distincts et incompatibles... (Une idée stupide dans la conception du langage C) Faut surtout pas faire ça, pour des raisons évidentes de lisibilité, mais c'est pour montrer que techniquement, les deux types sont distincts.
La notation ci-dessus c'est pour l'initialisation des structures lors de la déclaration d'une variable. Les points indiquent des _désignateurs_ de champs
struct Point {
float x, y;
};
// à l'ancienne
struct Point p1 = { 1.2, 3.4};
// avec désignateurs
struct Point p2 = {
.x = 1.2,
.y = 3.4
};
// dans le désordre si on veut
struct Point p3 = {
.y = 3.4,
.x = 1.2
};
// ceux qui ne sont pas cités sont à 0, 0.0 ou NULL selon type
struct Point origine = {
// .x = 0.0;
.x = 0.0 // correction : pas de point virgule
};
On peut aussi s'en servir dans une affectation. Application
Element *e;
...
*e = (Element){ .nombre = n, .suivant = liste->premier};
Lire : on met dans *e un Element dont le champ nombre contient n, et suivant (etc)
> c'est de vous ?
j'assume :) C'est rédigé un peu raide, j'étais de mauvais poil ce jour-là, parce que j'en avais marre de voir de lire des tutoriels foireux. En gros, ça correspondait à des trucs que je racontais en 2ieme année d'IUT info dans un petit cours de C (dont je ne suis pas spécialiste), du temps où j'étais censé travailler pour être payé.
@rouloude Ok merci pour les explications, donc le ".mavariable" permet d'initialiser la bonne variable avec la valeur souhaitée comme les paramètres dans les fonctions en soi.
@michelbillaud Super les explications, oui on peut se tutoyer, ça sera plus simple
je vois toutes les manières différentes d'initialiser une structure merci.
Par contre comme disait rouloude, le ";" ne doit pas être présent ?
Donc ici pour struct Point Origine ligne 25, le point-virgule devrait disparaître ?
Pour le PDF, tant que c'est compréhensible, moi ça me va
Et on peut même utiliser cette écriture dans l'appel d'une fonction. Exemple: la fonction TTF_RenderText_Blended() attend en troisième paramètre une structure de type SDL_Color. Et moi, cette structure, elle m'enquiquine, car j'ai pris l'habitude de coder les couleurs rgb sous la forme 0xrrggbb. J'ai donc écrit trois macros R, G et B qui extraient la couleur voulue à partir de la couleur codée 0xrrggbb. Et pour l'appel à la fonction, je fais
Le SDL_Color entre parenthèses c'est ce qu'on appelle un cast c'est bien ça ? Pas besoin de mettre un nom de structure entre (SDL_Color) et les accolades {} ?
Oui, c'est bien un cast. Il indique que ce qui va suivre doit être compilé comme étant une variable de type SDL_Color. Et SDL_Color est un typedef: voir ici
Ce qu'il y a de bien avec ce système, c'est que tout est réglé à la (pré)compilation, aucun calcul n'est fait à l'exécution (edit: si x est une constante, bien sur).
- Edité par edgarjacobs 25 avril 2024 à 18:59:22
On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent
Bon principe : on introduit des abstractions pour se simplifier la vie ensuite.
Histoire de chipoter [1], je dirais que c'est une déclaration de "compound literal" (une donnée composée définie et initialisée "sur place", en plein milieu d'une expression) plutôt qu'un cast [2].
Constraints Unless the type name specifies a void type, the type name shall specify atomic, qualified, or unqualified scalar type, and the operand shall have scalar type.
- Edité par michelbillaud 26 avril 2024 à 11:01:19
Le décalage de bits pour avoir la bonne valeur pour la couleur c'est pas mal, je retiens !
Pour les compound literal intéressant, si je comprends bien on parle de :
- "compound literal" pour les structures définies et initialisés
- cast pour toute autre type de variable (int, long, double, float, char, string tout ça signed ou unsigned) déjà initialisés et dont on veut juste changer le type sur le moment
Faudrait regarder dans les discussions du comité de normalisation, ça serait pas étonnant que le "braced initializer" (introduit dans quelle version du langage) soit présenté comme une généralisation du cast (présent dans le C d'origine)
En tout cas, ça donne plusieurs manières de faire avec les scalaires
void fun() {
float f;
f = (float) 1;
f = (float) {1};
f = (float) {1.23};
int i;
i = f;
i = (float) f;
i = (float) { f };
}
- Edité par michelbillaud 26 avril 2024 à 14:04:30
On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent
On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent
On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent
On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent
On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent