aide sur le realloc - C - Programmation
Marsh Posté le 04-01-2006 à 05:19:40
Bonjour bossgama,
Avant tout, votre test portant sur la taille de chaîne de caractère (taille = strlen(chaine)) pour tester que la réallocation s'est bien effectuée n'est pas correct dans l'aboslu. A priori, il est logique de pouvoir faire cela, mais même si un contenu n'a pas changé, une variable a pu changer d'adresse ! Il est préférable de tester directement la valeur du pointeur réalloué (car c'est bien lors de la réallocation que le contenu originel est recopié, c'est tout de même une spécificité faible par rapport à l'obtention d'une nouvelle adresse mémoire et c'est cette nouvelle adresse qu'il faut plutôt tester).
Votre erreur, vient de ce que votre fonction reallocation prend votre pointeur par valeur. Autrement dit, vous avez oublier que vous vouliez envoyer un pointeur par "adresse" : votre pointeur étant un <char *>, la variable chaine que vous recevez en paramètre de la fonction reallocation doit avoir comme type <char **> pour être un pointeur sur un <char *> si vous voulez pouvoir modifier cette variable.
Par conséquent, votre fonction reallocation effectue bien le travail de reallocation (avec succès ou non) dont la nouvelle adresse de réallocation va dans votre variable temporaire tmp. Seulement, lorsque vous écrivez 'chaine = tmp', c'est une variable locale nommé 'chaine' à laquelle vous transférer l'adresse nouvellement obtenue de la réallocation (réussit qui du plus est !).
En sortie de fonction de 'reallocation()', tmp et chaine sont détruites puisqu'elles sont variables locales et l'adresse de la réallocation est perdue !
Vous pouvez remarquer aussi, dans votre cas, le cas de votre compilateur, que non seulement la réallocation a déplacé le contenu originel de votre chaîne de caractère (de la fonction main()) longue de 337 caractères mais qu'en plus cette même réallocation (fonction primitive de votre compilateur) a détruit le contenu originel de votre chaîne en y mettrant probablement que des zéros. Le résultat s'est alors vérifié par : taille = 0 dans votre fonction main en ligne 48 d'après votre commentaire... sur d'autre compilateurs, le résultat aurait pu être tout simplement taille == 337 tout simplement car la fonction primitive de réallocation du C n'aurait pas toucher au contenu originel de votre espace mémoire.
Mais en aucun cas, le résultat ne peut être 'taille == 420' !!!!
C'est pourquoi, il faut faire très attention dans l'avenir à tester aboslument la nouvelle adresse que vous obtenez après réallocation. Le test du nouveau contenu après réallocation n'est pas nécessaire... à part assertion forte ou présomption de mauvais fonctionnement de votre système ou compilateur, c'est tout bonnement inutile.
CONCLUSION :
1°) votre fonction reallocation() doit prendre un <char **chaine> et vous devez écrire <* chaine> partout où vous avez auparavant écrit seulement <chaine> : par exemple, '*chaine = tmp' au lieu de 'chaine = tmp'. Ainsi la variable 'chaine' sera bien "persistente" dans sa valeur au travers de l'utilisation de votre fonction reallocation().
2°) Votre "test" 'taille = strlen(chaine)' ne donnera jamais 420 dans votre exemple tout simplement parce que originellement votre chaîne de caractère contenait 337 caractères et qu'il n'y aucune raison qu'après réallocation, la chaîne de caractère soit différente ! heureusement.
Donc même après correction de votre fonction reallocation(), dans votre fonction main(), à la suite de l'instruction 'taille = strlen(chaine)' après réallocation, vous obtiendrez toujours la valeur 337 originel, ce qui est bien heureux puisque c'est la longueur de la chaîne de caractères "Le Centre de Sécurité , TechNet propose des outils de sécurité, ... doit de proposer aux professionnels de l'informatique afin de les aider à sécuriser leurs systèmes". Cette chaîne de caractère ne changeant pas après réallocation (et pourquoi changerait-elle ?), le résultat est toujours 337 comme indiqué dans votre commentaire ligne 42.
REMARQUE :
Ne confondez pas la fonction strlen() qui donne la longueur de votre chaîne de caractère avec une fonction (qui n'existe pas en C) et qui donnerait "la table de la zone mémoire allouée à une adresse mémoire".
Autrement dit, si vous allouer 400 'char' pour une variable 'chaine" et que vous placiez une chaîne de caractère longue de 337 caractères dans votre 'chaine' alors strlen(chaine) = 337, ce qui est normal. Et vous voyez que cela n'a rien à voir avec la taille mémoire allouer à 'chaine' qui est de 400 'char'.
UN PETIT CONSEIL :
Ne sous-estimez pas la difficulté sémantique que représente les pointeurs en C. Lorsque l'on dit qu'une variable est un pointeur, cela signifie simplement que le contenu de cette variable n'indique pas la valeur réellement "utile" de cette variable mais plutôt que cette valeur en question donne l'adresse d'une zone mémoire contenant la valeur "utile" du pointeur en question.
Ainsi, il ne faut pas être troubler ou leurer par l'écriture fonction( int * n )... n est toujours une variable de locale ! Mais n étant un pointeur, si l'on écrit *n = 3, en mémoire, la zone dédié à n est modifié en valeur 3. Cependant si l'on change la valeur même de n, c'est-à-dire qu'on lui affecte une nouvelle adresse, alors il n'y a aucune raison pour laquelle une variable globale serait modifier outre mesure. Si donc, dans une autre fonction test(...), vous utilisez 'fonction( x )' avec x une variable de type 'int *', il n'y a aucune raison (jamais !) pour laquelle x serait changé. Son contenu oui, peut changer, mais x lui-même non ! En gros, il pointera toujours sur la même zone mémoire qu'avant l'appel à la fonction 'fontion(x)'.
Bonne année et bonne continuation.
P.S. : c'est un test donc ça va mais il faut éviter de vous habituer à mettre des 'exit()' partout dans vos programmes.
C'est fonctionnellement succeptible d'être incorrect et vous vous préparez à souffrir de violentes modification en masse de vos codes par la suite
Marsh Posté le 04-01-2006 à 09:15:54
bossgama a écrit : Salut, j'ai un bug dans mon programme et je n'arrive pas à voir pourquoi. Je sais que c'est lié à une mauvaise réallocation de mon tableau, mais pourquoi... |
Lire mes commentaires. Poser des questions si besoin est.
Code :
|
Marsh Posté le 04-01-2006 à 12:06:36
Bonjour Pheattarak,bonjour Emmanuel Delahaye!
Merci pour vos réponses!
Citation : |
Je comprends pas bien ce que vous voulez me faire faire. Pourquoi je voudrais envoyer mon pointeur par adresse ici?
Citation : |
J'utilise Dev C++, et le compilateur GCC.
Le résultat obtenu dépend de mon code, mais c'est vrai que j'ai eu plusieurs fois le cas ou la taille originale etait conservée apres la réallocation.
Citation : Ne sous-estimez pas la difficulté sémantique que représente les pointeurs en C. |
Ca y a pas de danger .
Citation : Lire mes commentaires. Poser des questions si besoin est. |
J'ai repris ton code mais à la compilation j'ai le même problème que ce que j'avais rencontré avant. En fait quand je réalloue la chaine, par exemple en mettant une taille plus petite (une chaine de 600 caracteres devient une chaine de 420 caracteres), il me coupe bien la fin de mon texte, mais me rajoute des caracteres en plus. Par exemple, l'execution de mon programme donne pour 420 caracteres une chaine de taille 427, avec des caracteres qui ont été ajoutés ou qui font partis de l'ancienne chaine, pour x raison. Est-ce que je me suis trompé dans mon code ou est-ce que c'est un problème récurrent de realloc?
Au fait on n'a pas besoin de string.h pour utiliser strcpy. Celui ci est contenu dans stdlib.h. Ca ne marche pas par contre si on enregistre le fichier en .cpp et non en .c, mais c'est normal. Sinon y a un chaine; qui traine avant l'allocation mémoire dans le main. Erreur ou partie manquante, car à la compilation il ne comprend evidemment pas ce que c'est puisque chaine est initialisé apres.
Marsh Posté le 04-01-2006 à 12:07:52
Je reposte mon nouveau code à tout hasard.
Code :
|
Marsh Posté le 04-01-2006 à 14:22:39
bossgama a écrit :
|
Normal. Je t'ai indiqué qu'il y avait un problème de conception (et je ne suis pas le seul à te l'avoir signalé). Je répète :
Modifier un paramètre est sans effet sur la valeur initiale de la variable. Pour agir sur cette valeur, il faut passer l'adresse de la variable. Or tu te contente de passer la valeur du pointeur. C'est dur à comprendre ?
Citation : En fait quand je réalloue la chaine, par exemple en mettant une taille plus petite (une chaine de 600 caracteres devient une chaine de 420 caracteres), il me coupe bien la fin de mon texte, mais me rajoute des caracteres en plus. |
Mais non, il n'ajoute rien. Seulement comme il a coupé brutalement la chaine, il manque un 0. A toi de le rajouter au bon endroit. Le C, c'est rustique. Il faut savoir ce que l'on fait...
Citation : Par exemple, l'execution de mon programme donne pour 420 caracteres une chaine de taille 427, avec des caracteres qui ont été ajoutés ou qui font partis de l'ancienne chaine, pour x raison. Est-ce que je me suis trompé dans mon code ou est-ce que c'est un problème récurrent de realloc? |
C'est un problème inhérent à realloc(). Il ne sait pas qu'il y avait une chaine, et il ne va pas mettre de zéro final par magie... Tu prends le risque de modifier une chaine, à toi de veiller à ce que le 0 final soit remis à sa place. Il faut être responsable de ses actes.
Citation : Au fait on n'a pas besoin de string.h pour utiliser strcpy |
.
Bien sûr que si.
Citation : Celui ci est contenu dans stdlib.h |
.
Ce n'est pas garanti par la définition du langage. Pas portable.
Citation : Sinon y a un chaine; qui traine avant l'allocation mémoire dans le main. Erreur ou partie manquante, car à la compilation il ne comprend evidemment pas ce que c'est puisque chaine est initialisé apres. |
J'ai rien compris.
Marsh Posté le 04-01-2006 à 14:50:20
Citation : Normal. Je t'ai indiqué qu'il y avait un problème de conception (et je ne suis pas le seul à te l'avoir signalé). Je répète : |
Un peu, je comprends relativement la théorie des pointeurs, mais j'ai parfois du mal à l'appliquer, sans parler des double pointeur, et d'après ce que vous me dites, il faudrait que j'utilise un double pointeur. Par contre je ne vois pas encore bien comment. Les quelques essais que j'ai fait avec un double pointeur pour l'instant ne marche pas.
Citation : Mais non, il n'ajoute rien. Seulement comme il a coupé brutalement la chaine, il manque un 0. A toi de le rajouter au bon endroit. Le C, c'est rustique. Il faut savoir ce que l'on fait... |
AHHH d'accord. c'est vrai qu'en y réfléchissant c'est logique, mais je n'avais pas du tout pensé à ca. Je vais réfléchir à comment coder ca.
Citation :
|
Ce 'chaine;' la (en gras).
Mais ce n'est peut-être pas très important.
Marsh Posté le 04-01-2006 à 15:00:49
bossgama a écrit :
|
Si tu trouves les doubles pointeurs compliqués (moi aussi), tu peux toujours utiliser la méthode 'malloc()' ou 'fopen()' (déjà signalé, non ?) qui consiste à retourner l'adresse. Charge à l'utilisateur de la stocker et de la tester avant usage... Rien de nouveau.
Citation : Les quelques essais que j'ai fait avec un double pointeur pour l'instant ne marche pas. |
Etant donné que tu ne montres pas tes essais, je ne vois pas comment on pourrait t'aider...
Citation : Ce 'chaine;' la (en gras). |
Tu peux le retirer.
Marsh Posté le 04-01-2006 à 15:05:15
Citation : |
Mon dernier code, celui qui à l'air de marcher le mieux (même si il bug quand même), est celui-la:
Code :
|
Je crois que c'est le caractere de fin qui fait planter mais sinon il me semble que ca marche.
Edit: Si je mets :
Code :
|
ca à l'air de marcher. Je ne fais pas d'erreurs de conceptualisation là, non?
Marsh Posté le 04-01-2006 à 15:42:59
bossgama a écrit : Mon dernier code, celui qui à l'air de marcher le mieux (même si il bug quand même), est celui-la: |
Tu fais un peu n'importe quoi, tu ne tiens pas compte de ce qui a déjà été dit (sizeof (char) == 1, par exemple) et tu choisis un exemple trop compliqué pour illustrer un problème simple.
Par exemple.
"J'ai une variable qui vaut 3."
int x = 3; |
"j'appelle une fonction inc(???) et je veux qu'ensuite la variable vaille 4. Comment coder inc() et comment l'appeller ?"
Si tu sais répondre à ça, ton problème est résolu.
Il faut apprendre à traiter les problèmes un par un.
Marsh Posté le 04-01-2006 à 16:29:51
Citation : "J'ai une variable qui vaut 3." |
tu veux dire un truc comme ca?
Code :
|
ca ne m'aide pas particulièrement .
Marsh Posté le 04-01-2006 à 16:30:29
bossgama a écrit : ca ne m'aide pas particulièrement . |
Normal, tu n'as apparemment pas compris le fonctionnement du passage par pointeur.
Code :
|
Marsh Posté le 04-01-2006 à 16:48:29
Citation : Normal, tu n'as apparemment pas compris le fonctionnement du passage par pointeur. |
Effectivement je ne dois pas comprendre , car je ne vois pas de différence entre ce que tu proposes et ce que moi j'ai proposé, à part l'absence de définition du pointeur dans le main de ton code. Dans mon code, je passe bien l'adresse de a définie dans le main, à la fonction inc(), non?
Marsh Posté le 04-01-2006 à 17:32:47
Dans ton code, tu retournes la valeur modifiée.
Dans mon exemple, c'est le paramètre passé à la fonction qui est directement modifié.
Marsh Posté le 04-01-2006 à 17:42:03
bossgama a écrit :
|
Un peu, mais c'est beaucoup trop compliqué.
Code :
|
donne
|
Il faut que tu apprennes à raisonner simplement. Ton esprit est trop tordu.
Maintenant, tu fais pareil avec un pointeur sur char :
Citation :
|
Marsh Posté le 04-01-2006 à 18:13:57
d'accord, je vois ce qui se passe. Mais si je veux adapter cet exemple à une chaine de caractères que je veux réallouer, il faut dc que je passe l'adresse du pointeur, donc &chaine, et que je travaille dans ma fonction reallocation avec un double pointeur? hum, difficile. Je vais chercher un peu.
Marsh Posté le 04-01-2006 à 18:43:16
ca devrait etre quelque chose dans ce gout là pour la fonction main non? Par contre j'arrive pas à voir comment on ecris la fonction reallocation en fonction du double pointeur.
Code :
|
Marsh Posté le 04-01-2006 à 19:53:05
Qu'est-ce qui cloche dans cette fonction? La réallocation ne se fait plus, et sans message d'erreur.
Code :
|
Marsh Posté le 04-01-2006 à 20:10:48
A peu près tout cloche dans ta fonction.
Déjà ce que tu veux, c'est réallouer un char*, pas un char**.
Il faut donc déréférencer ton pointeur chaine_tmp.
Ensuite, c'est tmp qu'il faut tester à NULL, et non chaine_tmp qui n'a pas encore été modifiée.
Et puis tu as commenté la ligne qui réaffecte *tmp à ta chaîne. Qui est fausse, puisque dans cette ligne tu tentes d'assigner un char à un char*.
Code :
|
Marsh Posté le 04-01-2006 à 21:07:11
Elmoricq a écrit : A peu près tout cloche dans ta fonction.
|
Ah vi d'accord! Je vois mon problème. J'avais zappé que je réallouais sur la valeur de chaine_tmp et non la valeur pointée par chaine_tmp. C'est pour ca que ca ne marchait pas. Plus que le problème de la fin de chaine à régler et ma fonction marche ensuite.
Merci à tous de m'avoir consacré du temps
Marsh Posté le 04-01-2006 à 22:34:03
bossgama a écrit : d'accord, je vois ce qui se passe. |
Alors fait l'exo que je t'ai proposé et montre le résultat..
Citation : |
Ca, c'est l'étape suivante. Apprend à travailler par étape et à consolider tes connaissances. Traiter les problèmes un par un...
Marsh Posté le 04-01-2006 à 22:49:11
Citation : Alors fait l'exo que je t'ai proposé et montre le résultat.. |
Je n'avais pas vu que tu avais edité ton post . Ca devrait donner ca non?
Code :
|
Citation : Ca, c'est l'étape suivante. Apprend à travailler par étape et à consolider tes connaissances. Traiter les problèmes un par un... |
En fait j'ai un projet à rendre vendredi, et je suis à la bourre. C'est pas trés grave si je finis pas, mais j'aimerais avoir des fonctions qui marche. Mon algorithme est pret et presque fonctionnel, mais en le compilant je me suis rendu compte que j'avais un problème avec mon allocation mémoire, et incidemment avec ma gestion des pointeurs . C'est pour ca que j'essaye d'activer dans un premier temps pour rendre le projet et que je me replongerais sur les parties manquantes ou un peu faible après, pour être sur d'y arriver.
Pour en revenir à ma fonction, j'arrive finalement à reproduire mes chaines, mais il me reste l'histoire de la fin de chaine à gérer. C'est bien avec un '\0', non?
Donc si je mets un
Code :
|
dans la boucle qui teste la valeur renvoyée par realloc, et sachant que les chaines de caractères sont indicées de 0 à n-1, ca devrais bien terminer ma chaine, non?
Marsh Posté le 04-01-2006 à 22:50:44
bossgama, ce fût dur ?
Pour répondre à une de vos questions concernant les pointeurs et double pointeurs etc. En fait, tout est très simple.
Double pointeur ou triple, une variable est une variable. En cela, elle possède un contenu.
En C, le contenu d'une variable (pas un pointeur) est la variable elle-même. Par exemple : dans le cas de <int n = 10> alors partout où vous écrirez n, ce sera sa valeur (ici 10) que vous utiliserez. Et lorsque vous écrivez n=3, c'est bien n que vous changer : autrement dit, son contenu.
Maintenant, lorsque vous avez affaire à un pointeur le contenu du pointeur est toujours la variable utilisée. Autrement dit, <int * p> veut dire que si l'on utilise p, c'est l'adresse mémoire d'un <int> que l'on obtient.
Une des premières erreure ici est souvent d'écrire <int * p = 10> car il ne faut pas oublier que <int * p> indique que l'on demande au compilateur de créer un pointeur p sur un <int> mais p ne pointe sur rien pour le moment. Et il faut alors allouer un espace mémoire suffisament grand pour contenir ce sur quoi p va pointer, ici un <int>.
Donc logiquement, pour écrire l'équivalent de <int n = 10> en passant par un pointeur, on pourrait écrire <int * p; p = (int *) malloc(sizeof(int)); *p = 10;>... bien entendu, normalement il faut tester l'adresse que l'on obtient du malloc().
Revenons aux pointeurs. Pour un pointeur p (simple, double, triple ou autre), lorsque l'on écrit p, c'est la valeur de p que l'on utilise, c'est-à-dire l'adresse contenu dans p.
Si p est un pointeur, par exemple <int *** p>, alors ***p est un int. De même que **p est un int* et *p est un **int. C'est une méthode mnémotechnique pour se rappeller du type d'un pointeur : on compte le nombre d'étoile devant le p (exp. **p, 2 étoiles), ce nombre d'étoile on le retire du nombre d'étoile utilisée dans la définition de p (exp. int *** p). On a donc 3-2 = 1 étoile, donc **p est de type int * (une étoile seulement).
Dès lors que cette "règle" est comprise, on sait tout de suite à quoi on a affaire lorsque l'on utilise un pointeur : si c'est toujours un pointeur ou si c'est enfin un élément d'un type donné.
EXEMPLE dans les fonctions :
void f( char ** p ) ... p est donc de type char**; *p est de type char* et **p est de type char.
Dans la fonction f, ce que l'on veut c'est avoir l'adresse d'une variable dont le type est <char *> pour pouvoir en modifier directement le contenu. En cela, char**p ne définit pas un tableau à deux dimension mais juste un pointeur sur un tableau à une dimension. Le but étant de pouvoir soit changer le contenu même du tableau soit le contenu même du pointeur qui pointe sur le tableau (c'est-à-dire p).
J'ai l'impression que ce n'est pas clair.
Bref, le problème est que vous ayez résolu votre problème.
Il vous suffira de retenir que lorsque vous écrivez quelque chose avec un pointeur, son type est facilement déterminé en retranchant le nombre d'étoile utilisé dans l'écrire de votre "variable" par rapport au nombre d'étoile de la définition même de la variable :
int ****** p ---> p est un int******
---> *p est un int*****
---> **p est un int****
---> ***p est un int***
---> ****p est un int**
---> *****p est un int*
---> ******p est un int, ce n'est plus un pointeur. Enfin !
Marsh Posté le 04-01-2006 à 23:04:10
Pheattarak a écrit :
|
Ca c'est sur.
Mais bon maintenant il me semble avoir compris dans les grandes lignes ce qu'il se passait grâce à votre aide, et à celle d'Elmoricq et d'Emmanuel Delahaye.
Par contre je vais avoir besoin de plus de pratique pour vraiment maitriser ça.
En fait, le plus dur pour moi c'est de réaliser à quoi j'ai affaire, notamment dans les passages de paramètres entre fonctions.
Dans votre explication, par exemple:
Citation : int ****** p ---> p est un int****** |
Je sais que j'aurais du mal pour savoir à quoi j'ai affaire et avec quoi je dois travailler (***p, ou **p par exemple). Mais bon comme j'ai dit, il faut que je m'entraine plus
Marsh Posté le 04-01-2006 à 23:04:18
bossgama a écrit : Ca devrait donner ca non? |
Ben non...
Code :
|
Citation :
|
Pourquoi est-tu à la bourre ? Pas normal. Tu viens d'avoir 2 semaines de vacances... Le but de l'école c'est d'apprendre. Il vaut mieux rendre moins de choses mais maitrisées que tout approximatif. La prochaine fois tu consacreras plus de temps pour approfondir.
Citation : |
Ok.
Citation :
|
Tu t'es relu ? Tu me dis que les indices vont de 0 à n-1, ce qui est correct, et tu veux écrire en n, ce qui est bien sûr en dehors de la zone. Soit un peu logique. Tu viens de donner la solution et tu ne l'utilises pas...
Marsh Posté le 04-01-2006 à 23:18:51
Emmanuel Delahaye a écrit : Ben non...
|
Citation : /* -ed- quel est le type de *p ? et celui de "word" ? |
J'ai compilé, et j'ai réfléchi, et chez moi ca marche, mais c'est vrai que je l'ai fait assez vite. Par contre je ne vois pas ce qui ne va pas. *p est un char* et word aussi non?
Citation : Pourquoi est-tu à la bourre ? Pas normal. Tu viens d'avoir 2 semaines de vacances... |
Je suis d'accord, mais j'ai travaillé pendant ces vacances ( et même plus que d'habitude ), mais le projet est assez long, puisqu'il s'agit de faire de la mise en page de textes. D'autant que je ne suis pas le seul à ne pas avoir fini (en fait c'est le cas de 80% de ma classe).
Citation : Il vaut mieux rendre moins de choses mais maitrisées que tout approximatif. |
Je suis bien d'accord, mais comme là je suis un peu pris par le temps, je me presse un peu, mais je compte evidemment me remettre dessus quand j'aurais plus de temps libre pour bien assimiler ces problèmes. De plus mon algorithme marche (ajout de marges à un texte, et la gestion de la ponctuation), mais comme le problème d'allocation me rajoutait des caractères en fin de chaine, j'ai réalisé que j'avais des erreurs et en me penchant dessus j'ai rencontré d'autres problèmes. Enfin bref... tout ca pour dire que je m'y remettrais plus calmement ce week end et la semaine prochaine.
Marsh Posté le 04-01-2006 à 23:25:22
Citation : Tu t'es relu ? Tu me dis que les indices vont de 0 à n-1, ce qui est correct, et tu veux écrire en n, ce qui est bien sûr en dehors de la zone. Soit un peu logique. Tu viens de donner la solution et tu ne l'utilises pas... |
My bad! C'est parce que je réfléchissais en tenant compte des caractères que le programme affiche.
Cela dit ca ne marche pas, mais là ca doit etre la fatigue. Je vais y réfléchir encore un peu mais je crois que je comprendrais mieux demain.
Code :
|
Marsh Posté le 04-01-2006 à 23:27:32
bossgama a écrit :
|
Non. "word" est une chaine de caractères donc on peut l'assimiler à un char *, ùmais si p est défini char *p, *p est un char et non un char *. C'est donc incohérent, et mon compilateur ne se prive pas de me le rappeler :
Citation : |
Il y a donc un problème sur le type du paramètre.
Tu as un pointeur sur char
char *p; |
dont tu veux passer l'adresse à une fonction.
func (&p); |
Quel doit être le type du paramètre ?
Si tu as un doute, reprend l'exercice précédent avec inc().
Marsh Posté le 04-01-2006 à 23:40:30
bossgama a écrit : Cela dit ca ne marche pas, mais là ca doit etre la fatigue. Je vais y réfléchir encore un peu mais je crois que je comprendrais mieux demain.
|
Ok. Le reste sera bon tel quel quand tu auras changé le type de tmp.
Marsh Posté le 04-01-2006 à 23:59:31
Citation : si p est défini char *p, *p est un char et non un char *. |
En relisant, c'est ce que disait Pheattarak dans son post précédent.
Citation : Quel doit être le type du paramètre ? |
J'ai un doute, mais intuitivement, je dirais que a est un char*, que l'on passe à la fonction funct, donc, p doit etre de type char **.
J'ai bon sur ma compréhension du code?
Marsh Posté le 05-01-2006 à 00:20:52
bossgama a écrit :
|
Ok. Tu comprends donc pourquoi le code est faux.
Citation :
|
Intuitivement, c'est bon, mais on ne réalise pas de grands projets à coup d'intuition mais de certitudes. C'est effectivement char **, mais ce n'est pas du hasard :
Si on généralise. Pour passer l'adresse d'un type T, on utilise un pointeur sur ce type, soit T *
etc.
(les espaces autour des '*' n'ont qu'un rôle documentaire et non sémantique)
Marsh Posté le 05-01-2006 à 00:45:27
Je crois avoir à peu près compris. J'essairais de repratiquer ca ce weekend pour mieux comprendre et bien assimiler ces notions. Merci beaucoup pour votre aide à tous.
Vraiment, merci!
Marsh Posté le 05-01-2006 à 15:49:45
Wow bossgama !
Ce fût vraiment difficile hein . Le plus important est que vous sentiez avoir compris. J'espère que vous rendrez quand-même ce projet et qu'il sera "fonctionnel".
Si je peux me permettre un conseil (encore un peut-être), ne vous exercez pas à tout faire en même temps : les chaînes de caractère en C, les pointeurs, les passages de paramètres etc. Reprenez chacune une par une isolément et comprenez, par exemple, que les pointeurs en C sont des variables comme les autres mais que ce qui fait la difficulté c'est leur utilisation. Autrement dit, de se fixer un objectif dans sa réalisation et de voir où et comment les pointeurs peuvent intervenir.
Car de leur utilisation dépend la signification de ce que vous ferez avec eux. Dans un cas, ce sera pour avoir l'adresse d'une donnée (un pointeur sur quelque chose) et dans d'autres cas, ce sera peut-être un tableau (un pointeur sur une zone mémorielle). Dans tous les cas, c'est sûr qu'un pointeur pointe sur une zone mémorielle, cela n'avance à rien de dire la chose bien entendu mais il y aune différence tout de même dans son utilisation.
En gros, et pour conclure par un exemple, si j'ai une chaîne de caractère et que je veux pouvoir changer son contenu alors il me suffit de connaître l'adresse de la zone mémoire qui contient cette chaîne.
Si je prévois 500 char pour ma chaîne de caractères, après allocation réussie, j'aurai en effet une adresse mémoire dans ma variable c (char * c), qui pointera sur ce zone mémoire. Si je veux donner l'adresse de cette zone mémoire à une fonction pour qu'elle aille trifouiller dans ma chaîne de caractère, je peux utiliser c (seulement) dans l'appelle à cette fonction (disons f) : f(c). 'f' reçoit alors le contenu de 'c' qui est l'adresse de ma chaîne de caractère.
Maintenant, si vous voulez écrire une fonction m qui modifie la position du contenu de ma chaîne de caractère en mémoire, il y a aucun problème pour ça. Mais si vous voulez en plus qu'en appelant la fonction, cette dernière modifier pour vous le contenu même de l'adresse que vous avez dans votre pointeur c, il vous faut passer un pointeur sur c à la fonction : c'est le seule moyen en langage C de modifier une variable dans une fonction.
Si donc, votre fonction m doit pouvoir modifier une donné, elle devrai recevoir un pointeur sur cette donnée. Si de plus, vous voulez modifier le contenu de c (qui est un char *) alors vous devez tout simplement passé un pointeur sur un 'char *' donc un 'char **'.
La suite, on vous a soualer avec. Mais ce que je voulais dire par là, c'est que la fonction m qui reçoit un char ** n'est pas une fonction qui reçoit un tableau à double dimension car c'est là la réelle difficulté du langage C : on ne sait jamais a priori à quoi sert un pointeur ! il pointe oui mais sûr quoi ? un élément qui existe, un élément qui va devoir exister, un tableau, une "donnée" (c'est-à-dire une instanciation d'un type pour reprendre le vocable du C++)... ?
Et vous auriez pu faire autrement dans votre programme. Pour ne pas utiliser de pointeur sur une chaîne de caractère, vous auriez pu faire une fonction < char * reallocation (char * c) >. La fonction reallocation aurait fait une réallocation de la chaîne de caractère c mais aurait rendu tout simplement le nouveau pointeur de réallocation (contenu dans tmp par exemple). La fonction aurait fini par < si la réallocation fût une réussite alors "return tmp;" >.
Dans votre programme principale vous auriez utiliser aussi une variable temporaire < realloc_tmp = reallocation( c ); if (realloc_tmp != NULL) ... >.
Vous vous appercevez d'une chose assez "rigolote"... c'est que votre fonction reallocation n'a aucun intérêt ! puisqu'elle s'appelle "realloc()" déjà en C. Et le fait de l'avoir encapsulé dans une fonction n'y change rien car en sortie de fonction, vous êtes obligé de nouveau de tester si votre pointeur c est différent de celui que vous aviez auparavant.
Par là, je veux dire que si vous utiliser la fonction "realloc()" du C, à un moment donné, vous serez de toute manière obligé de tester sa valeur de retour. Donc ici, inutile de faire une fonction "reallocation()" qui ne vous permets pas de ne pas avoir à faire ce test puisqu'apparemment, vous le faîtes après
Je ne sais pas si mon exemple est bon. Mais je voulais insister sur le fait que vos problèmes sont venus de votre manière d'écrire votre fonction "reallocation()" et que dans le futur ce sera (a priori) toujours ça le problème : le choix de la manière d'utiliser un élement du langage C plutôt qu'un autre.
RAPPEL : je finis par un petit rappel parce que dans l'un de vos post, bossgama, vous m'avez eu l'air d'hésiter dans la notion de type.
Emmanuel (il me semble), vous a bien dit qu'un word n'est pas un char etc. C'est important ! en C, tout type peut être assimilé à un autre, le compilateur le plus souvent ne vous dira rien si ce n'est un "warning". Maintenant, les compilateurs sont plus difficiles et vont crier plus fort, y aura des "error" à la place.
Cependant, tout ça ne doit pas vous mener dans une impasse : et retenez dès maintenant qu'un type est un type à part entière même s'il est "totalement" équivalent à un autre. En C, comme en langage typé en général, un type est un type ! deux types T1 et T2, même totalement identiques dans la définition, sont différents lorsqu'on les compare dans un langage typé. Ainsi deux élément t1 et t2 des types T1 et T2 sont de types différents et l'on ne peut pas logiquement écrire : t1 = t2.
"Heureusement", en C, on est libre de le faire car bon, des fois ça arrange bien lorsque l'on sait que T1 == T2, de dire t1 = t2. Au pire, on fait "t1 = (T1) t2" et hop, le C ne crie plus.
Mais c'est là que vous ne devez pas faire d'erreur surtout maintenant, si même un word tient en autant de place qu'un char, par définition un char ne possède que 256 valeurs et un word, en général, au moins 256 fois plus. C'est-à-dire que malgré la place mémoire qu'ils occupent, ces types sont totalement différents a priori.
A vous de savoir si vous voulez prendre des risque quant à associer l'un à l'autre allègrement... mais vous aurez un jour ou l'autre des problèmes si vous considérer qu'un char est un word, même si c'est le cas. Car une fois encore, c'est l'utilisation qu'on en fait qui fait toute la différence.
Pour en revenir au chaîne de caractères. Vous posiez la question, on finit une chaîne par un '\0' etc. et c'est encore Emmanuel je crois qui vous a corrigez lorsque vous avez placer cet élément '\0' en fin de tableau mais hors du tableau (de la chaîne de caractère).
1°) d'abord '\0' désigne le caractère de code (ASCII) zéro mais un char étant un nombre en langage C, vous pouvez aussi écrire c = 0 pour dire (c = '\0') puisqu'en effet le code ASCII de la fin de chaîne est bien la valeur "zéro". Cependant, et vous le verrez souvent par la suite, il y a bel et bien une différence entre '\0' et 0, c'est que '\0' est une manière de "nommer" un élément sans lui affecter directement une valeur au préalable.
2°) une autre convention est qu'un chaîne de caractère se termine toujours par '\0'. Autrement dit, dès maintenant, prenez l'habitude lorsque vous avez un pointeur de le mettre de suite à NULL, de même que dès que vous avez une chaîne de caractère, d'écrire *c = '\0' : mettre un '\0' en premier caractère de la chaîne.
Si vous oubliez l'un ou l'autre, un jour, vous aurez un problème.
Que la force soit avec vous ami
Marsh Posté le 05-01-2006 à 21:42:06
Hehe, vous aimez écrire, vous.
Plus sérieusement, je ne comprends pas bien votre explication
Citation : Autrement dit, dès maintenant, prenez l'habitude lorsque vous avez un pointeur de le mettre de suite à NULL, de même que dès que vous avez une chaîne de caractère, d'écrire *c = '\0' : mettre un '\0' en premier caractère de la chaîne |
ainsi que :
Citation : Vous vous appercevez d'une chose assez "rigolote"... c'est que votre fonction reallocation n'a aucun intérêt ! puisqu'elle s'appelle "realloc()" déjà en C. Et le fait de l'avoir encapsulé dans une fonction n'y change rien car en sortie de fonction, vous êtes obligé de nouveau de tester si votre pointeur c est différent de celui que vous aviez auparavant. |
Enfin dans le dernier cas, je crois que ce que vous voulez dire c'est que je pourrais dans mon code , au lieu de faire appel à ma fonction reallocation, directement mettre le code du realloc, puisque la fonction reallocation correspond exactement à ca.
Autre question, je reviens sur ma fonction , mais si je veux allouer une taille plus grande à ma chaine, il faut que je lui donne des caractères, non?
Par exemple :
Code :
|
Autre question, si via mon main, j'appelle une fonction bis, qui elle fait appel à la fonction réallocation, comment je dois écrire le passage de l'adresse dans ma fonction bis?
Code :
|
Je ne sais pas si je suis clair.
Marsh Posté le 05-01-2006 à 22:53:40
bossgama a écrit :
|
C'est simple. Lorsque tu définis une variable de type pointeur, il faut éviter qu'elle ait n'importe quelle valeur. Il est donc conseillé de lui donner une valeur connue dès sa définition, à savoir NULL, dans le doute (signifie invalide, mais est testable facilement), ou la bonne valeur tout de suite. Par exemple :
éviter
char *p; |
et 50 lignes plus tard
p = "hello"; |
car si entre temps tu as fait un
f(p); |
tu l'as dans le fion (comportement indéfini). même si dans la fonction, tu as mis un
|
préférer
char *p = NULL; |
après advienne que pourra, on pourra toujours tester p...
Autre statégie, donner toude suite la bonne valeur :
char *p = "hello"; |
ou
char *p = malloc(123); |
etc.
Il est aussi recommandé, si la valeur du pointeur n'est plus valide (suite à un free(), par exemple), de le forcer à NULL. Personellement, je profite de l'opérateur ',' pour réaliser une opération 'insécable' :
free (p), p = NULL; |
car, bien que ce soit techniquement possible, il faut être tordu pour glisser une instruction entre les deux :
free (p), printf ("%s\n", p), p = NULL; |
alors qu'avec
free (p); |
c'est beaucoup plus simple. Trop simple, trop tentant......
Marsh Posté le 05-01-2006 à 23:03:41
Citation : free (p), printf ("%s\n", p), p = NULL; |
ca ne marche pas ca, c'est bien ca? Si je comprend bien, tu libères p, et tu essayes d'afficher p, qui peut valoir n'importe quoi, d'où la nécessité de le mettre à NULL, pour être sur qu'il est invalidé.
Marsh Posté le 05-01-2006 à 23:09:25
bossgama a écrit : Autre question, je reviens sur ma fonction , mais si je veux allouer une taille plus grande à ma chaine, il faut que je lui donne des caractères, non? |
Euh, "donne des caractères", est une expression un peu vague, pas très scientique en tout cas. Je dirais qu'il faut agrandir le tableau de char. C'est tout. realloc() est tout indiqué pour ce travail.
Citation : Par exemple : |
Lit mes commentaires et tiens en compte STP. J'en ai assez de répéter les mêmes choses...
Code :
|
Autre question, si via mon main, j'appelle une fonction bis, qui elle fait appel à la fonction réallocation, comment je dois écrire le passage de l'adresse dans ma fonction bis?
Code :
|
Marsh Posté le 05-01-2006 à 23:13:29
bossgama a écrit :
|
Pour être plus précis, le free(p) ne modifie pas la valeur de p, simplement, la zone pointée étant maintenant invalide, y accéder peut provoquer n'importe quoi (comportement indéfini).
Le fait de le mettre à NULL augmente les chances, si il est malgré tout utilisé, que le dysfonctionnement soit
Ca facilite aussi le debug...
Néanmoins, ça n'empêche pas d'être vigilant...
Marsh Posté le 05-01-2006 à 23:17:35
Citation : # |
Je croyais que tu parlais de mon ex sizeof(char), qui etait égal à 1. le ; c'est juste une erreur que je n'avais pas vu, et qui maintenant est supprimé.
Citation : Ca aurait ete plus simple avec memset()... |
Je ne connais pas memset. Je vais aller voir ca sur le net.
Marsh Posté le 06-01-2006 à 16:08:43
Bonjour bossgama ,
> Hehe, vous aimez écrire, vous.
Ben oui et non. Disons que c'est mon gros défaut... je ne sais pas être si concis que ça et le pire, c'est que je déteste être mal compris. Du coup, mes phrases se font à rallonge. Désolé, je vais essayer de faire plus court.
Avant de continuer, Emannuel a écrit beaucoup de chose et a répondu à beaucoup de vos questions donc je vais juste répondre aux questions que vous me posiez suite à mon dernier post : ce sera plus clair entre nous.. enfin, j'espère.
>Plus sérieusement, je ne comprends pas bien votre explication
>
>>Autrement dit, dès maintenant, prenez l'habitude lorsque vous avez un pointeur de le mettre de suite à NULL, de même que dès que vous avez une chaîne de caractère, d'écrire *c = '\0' : mettre un '\0' en premier caractère de la chaîne
C'est une règle en programmation : "après la déclaration d'une variable, il faut penser à l'initialiser au plus tôt". On peut dire aussi "il faut éviter les déclarations et les remplacer par des définitions".
La différence entre déclaration et une définition c'est que dans le cas de la déclaration on dit "cette variable est comme ça" (autrement dit, on dit, cette variable est de ce type). Le problème c'est que la variable, a priori en C, n'est pas prête à l'emploi.
Et donc, si l'on ne fait que déclarer une variable et que l'on s'en sert par la suite sans lui donner une valeur "initiale" juste, le programme comportera par définition une erreur même s'il fonctionne bien. Je vais encore être obligé de faire long mais bon, allons-y pour un exemple.
Il arrive souvent que l'on déclare une variable et que la définition arrive naturellement par la suite :
Code :
|
On trouvera idiot ici, d'écrire < int i = 0; > qui est une définition (puisque i prend une valeur, dite initiale ici). Cependant, ce genre d'oubli peuvent produire beaucoup de petits "bugs" lorsque l'on y fait pas attention car entre la déclaration de la variable (donc sans que cette variable n'aît de valeur "correcte" ) jusqu'à son utilisation, il est possible que l'on oublie parfois de donner une valeur correcte à la variable en question.
Là, où on se dit "mais chuis pas bête quand-même" (pour être poli), il arrive que dans le cas des pointeurs, cette erreur de ne faire que des déclarations au lieux de définitions, soit carrément dangereux. Car voilà le problème des pointeurs vis-à-vis des autres variables : "on ne peut jamais, a priori, connaître l'utilisation du pointeur". C'est-à-dire qu'à un moment donné, on ne peut jamais être certains de ce que l'on veut ou va faire avec un pointeur. Dans l'absolu, c'est vrai aussi pour les autres variables (non-pointeurs) !.
Du coup, un pointeur étant un variable d'un type dit "pointeur sur... un type", on sait qu'il peut avoir deux valeurs particulières : une valeur NULL pour dire "je ne pointe sur rien" (par exemple) ou une valeur "numérique" autre que NULL et alors ça veut dire "je pointe sur quelque chose" (quelque chose de valide on l'espère !).
Mais voilà ! si l'on ne fait que déclarer un pointeur (sans l'initialiser donc), on est amener à écrire :
Code :
|
On voit bien ici, que p n'a pas été initialisé du tout donc il peut contenir la valeur NULL (par défaut) ou une valeur numérique autre que NULL. Donc il peut contenir n'importe quoi après sa déclaration : et c'est bien le problème !
Du coup, lorsque l'on arrive en ligne 5 du code et que l'on teste p avec NULL, on a tout faux ! puisque p n'a jamais été initialiser et donc quelque soit sa valeur à ce moment-là du code, le test ne signifie rien ! puisque p n'a aucune valeur qui soit représentatif de quoi que ce soit.
Le problème majeur qui arrive par la suite c'est que beaucoup de compilateur intialise toutes les variables (au moins globale) à zéro et les pointeurs à NULL. Et là, vous aurez une erreur que vous aurez extrêmement de mal à trouver car sur votre compilateur et votre système tout marchera. Puis lorsque vous passerez votre code sur les machines de la NASA, la navette elle scratches !!!
Donc en résumé, même si ça coupe un peu de temps machine et d'octets... il faut préférer, au moins par prudence, ne faire que des définitions : c'est-à-dire des déclarations suivie d'une initialisation par défaut.
Pfff, c'était long ah là là et c'était qu'une question . Bon la suite
> Vous vous appercevez d'une chose assez "rigolote"... c'est que votre fonction reallocation n'a aucun intérêt ! puisqu'elle s'appelle "realloc()" déjà en C. Et le fait de l'avoir encapsulé dans une fonction n'y change rien car en sortie de fonction, vous êtes obligé de nouveau de tester si votre pointeur c est différent de celui que vous aviez auparavant.
Vous avez tout à fait répondu vous-même... je disais justement que vous auriez pu utiliser realloc() au lieu de votre fonction réallocation(). Mais je voulais surtout insister sur le fait que dans le futur vous verrez, vous aller écrire tout plein de fonction et la majeure partie du temps, ces fonctions n'auront aucun intérêt sauf que d'allourdir votre code... c'est malheureux mais ça nous arrive tous je pense. En cas, moi, j'en étais le champion dans une autre vie hahaha.
Mais pour vous dire exactement pourquoi votre fonction réallocation() n'apportait rien, c'est tout simplement parce qu'elle ne fournit aucun résultat directement testable... je m'explique.
Vous avez un pointeur qui pointe sur une zone mémoire déjà alloué par malloc() (ou autre) et vous voulez agrandir la zone mémoire sur laquelle ce pointeur pointe. OK. Vous décidez de faire une fonction et c'est très juste même très utile. Mais votre fonction va faire quoi ? une réallocation effective... non ! c'est realloc() qui le fait. Alors que peut bien faire votre fonction de plus ?
La réponse c'est vous qui l'avez, c'est votre choix. Prenons un exemple, moi, je fais le choix de me dire que la fonction reallocation() que j'écris va "m'empêcher" d'avoir à vérifier si une réallocation s'est effectivement bien passé ou pas.
Ca peut sembler curieux de se demander si une réallocation a réussit ou non puisque de toute manière, ça ne change a priori rien sauf que si... Si j'ai un pointeur qui pointe sur une zone de 400 char en mémoire et que je veux 500 char, que j'utilise realloc(), comment puis-je savoir après la réallocation de combien de caractères je dispose ? 400 ou 500 ?
Car en C, on ne peut pas (a priori) connaître la taille d'une zone mémoire qui nous est alloué. Sauf, si juste après la réallocation (dans ce cas), je teste le nouveau pointeur obtenu et que je compare son adresse à NULL. Qu'on me corrige si je me trompe mais il me semble que si j'ai NULL en retour d'une fonction realloc() ça veut dire que la réallocation n'a pas eu lieu.
Donc dans mon exemple, si mon nouveau pointeur est NULL, je dispose toujours que de 400 char et si ça a réussit, je dispose de 500 char. Et moi, étant fénéant et souffrant de perte de mémoire chronique, je vais écrire quelque chose de plus explicite dans mon code en utilisant une fonction toute faite maison plutôt que d'utiliser realloc() et un test avec NULL par la suite... je vais donc par exemple écrire :
Code :
|
Bien entendu, j'aurai crée mon type bool avec les valeurs TRUE et FALSE pour vraie et faux. Cette fonction bien que ne faisant pas plus que realloc() a l'avantage néanmoins d'être plus claire dans son utilisation :
Code :
|
Juste une remarque dans votre code. J'ai cru voir à un moment que vous allouiez (taille + 1) caractères pour votre chaîne de caractère. Ceci n'est pas très sage. Vous ne pouvez pas le savoir a priori bien sûr, mais en ce qui concerne les chaînes de caractères en C, il existe des usages. Un des usages les plus importants étant : "lorsque l'on veut n caractère pour sa chaîne de caractère, c'est en effet n-1 caractère dont on dispose puisque l'on considère le caractère de fin de chaine ('\0') comme faisant partie intégrante de la chaîne".
Je ne sais pas si je m'avance en disant cela d'ailleurs car aucune règle précise n'est écrite à ce sujet. Mais par usage, on s'apperçoit bien que c'est le cas. Il serait donc préférable que nous ne fassiez pas cas particulier du caractère fin de chaîne... il fait partie des caractères de toutes vos chaînes de caractères et basta.
De même si vous écrivez des fonctions sur des chaînes de caractères un jour comme une fonction de copie de chaîne par exemple, copier le caractère de fin de chaîne comme si c'était un caractère comme un autre. Car mis à part le fait qu'il marque la fin d'une chaîne de caractère, il en fait partie intérante tout de même. Voilà
Vous posiez une question que je n'ai pas bien compris :
> Autre question, je reviens sur ma fonction , mais si je veux allouer une taille plus grande à ma chaine, il faut que je lui donne des caractères, non?
D'après l'exemple que vous donnez par la suite, j'ai compris que vous demandiez s'il faut "alimenter" la zone mémoire supplémentaire que l'on a obtenu si la réallocation à réussie. La réponse est évidemment : ça dépend de ce que vous voulez faire !
Par contre, une chose à savoir c'est que la fonction de réallocation (realloc()) en C, lorsque la réallocation est possible, recopie toutes les données de la zone mémoire originelle vers la nouvelle zone mémoire attribuée (si cette zone a été déplacée et non agrandie tout simplement).
Du coup, dans le cas de votre chaîne de caractère, tout le contenu de votre chaîne de caractère se retrouve intacte dans la nouvelle zone mémoire qui vous est attribué lorsque la réallocation est réussie. Vous n'avez donc rien à faire !
Le problème serait tout autre si à la place d'une chaîne de caractère, vous aviez un tableau de n objets de type T. Lorsque vous demandez une réallocation de taille m (plus grande) de la table et que la réallocation réussie, les objets de type T qui se trouve en fin de table dont le nombre est m-n ne sont pas initialisés. Selon votre programme et vos besoins, vous auriez alors peut-être tout intérêt à initialiser ses éléments. C'est un exemple comme un autre. Encore une fois, tout dépend de ce que l'on veut faire. Mais ce qui est sûr c'est que vos n premiers éléments de la table se retrouve identique dans la nouvelle table réallouée... heureusement
>Autre question, si via mon main, j'appelle une fonction bis, qui elle fait appel à la fonction réallocation, comment je dois écrire le passage de l'adresse dans ma fonction bis?
Tout est problème de concordance de types avant tout, puis enfin que ce que vous voulez faire soit bien fait. C'est bête à dire mais voilà, c'est ça. Alors en gros voilà un exemple :
Code :
|
Voilà. Et encore désolé, j'ai été vraiment concis cette fois-ci, c'est mon record
Encore tous mes voeux de bonne continuation le bossgama
Marsh Posté le 04-01-2006 à 01:03:51
Salut, j'ai un bug dans mon programme et je n'arrive pas à voir pourquoi. Je sais que c'est lié à une mauvaise réallocation de mon tableau, mais pourquoi...
J'ai regardé sur le forum, notamment le post de in_your_phion, et le site de Emmanuel Delahaye, mais je n'arrive malgré tout toujours pas à comprendre.
Est-ce que quelqu'un peut m'expliquer?