Mon journal précédent parlait de realloc dont on ne contrôlait pas la valeur de retour. Suite à ce journal j'ai été très surpris par le nombre de commentaires clamant que ce n'était pas important, que le noyau se chargerait de tuer le processus, que le programme planterait, … Je vais donc expliquer ce qui m'a amener à écrire ce journal.
Je codais un petit truc vite fait en C et j'avais besoin d'utiliser snprintf
. Cette fonction prend, en paramètre, la taille de la zone mémoire. Si cette zone mémoire ne suffit pas, elle retourne la taille qui aurait été nécessaire pour inscrire toute la chaîne de caractère. Il est difficile de savoir la taille à l'avance. Un simple "%d"
peut nécessité de 1 à plus de X caractères. Il faut donc, dans certains cas, le faire en deux fois. On évalue la taille qui sera généralement suffisante et on prévoit le cas où ce n'est pas suffisant. Ça donne un code qui ressemble à ça :
#define DEFAULT_SIZE 30
int size=0;
char * str=NULL;
char * tmp=NULL;
/* ... */
str=malloc(DEFAULT_SIZE * (sizeof * str));
if(str!=NULL) {
size=snprintf(str, DEFAULT_SIZE, /* ... */);
/* man page dit : "Thus, a return value of size or more means that the output
* was truncated." */
if(size>=DEFAULT_SIZE) {
/* snprintf retourne taille sans le '\0' */
size++;
tmp=realloc(str, size * (sizeof * str));
if(tmp!=NULL) {
size=tmp;
snprintf(str, size, /* ... */);
} else {
free(str);
str=NULL;
}
}
}
En écrivant la ligne tmp=realloc
, j'avais commencé par écrire str=realloc
. Je me suis arrêté net et j'ai pesté. J'ai pesté parce que je sais que c'est une erreur généralement acceptée. L'idée du journal m'était venue, il me manquait plus qu'un exemple concret dans un programme existant et utilisé.
Trouver un programme fut d'une simplicité enfantine. N'importe qu'elle programme qui semble manger beaucoup trop de mémoire est certainement bourré de ce genre d'erreur. J'avais remarqué conky il y'a bien longtemps. Avec un VmPeak
de 281212 kB et un VmData
de 82612 kB pour afficher, en texte, la date, la charge de la batterie, le nom du réseau Wifi et mon adresse IP, ça transpire le gaspillage.
Un git clone
et un grep -R realloc *
plus tard, j'avais une quinzaine d'exemple de realloc
foireux. Soit tous les realloc, sans exception. Les malloc sont aussi foireux.
J'ai fait un patch pour ce que je pouvais, rapidement, corriger. VmPeak
, sur la configuration par défaut, passe de 317916 kB à 211388 kB et VmData
de 189372 kB à 123836 kB. Une amélioration notable après environ 4 ou 5 realloc corrigés.
Comme a commenté "pasBill pasGates", à propos de gérer correctement les retours de fonctions comme realloc ou malloc :
[…] c'est une question d'hygiene de base de mon point de vue.
Ne pas le faire, ce n'est pas seulement manquer d'hygiène, c'est surtout faire preuve de laxisme. Non, on ne peut pas compter sur le magicien "OOM Killer" de Linux. Le noyau a pu être compiler sans ça. Non, attendre un SEGFAULT pour quitter le programme n'est pas une solution. Et, surtout, ce n'est pas une optimisation que de gagner un ou deux cycles processeurs en enlevant un if(x==NULL)
.
"pasBill pasGates" a aussi dit :
Arreter le programme peut-etre oui, selon ce que le soft fait, mais il faut le faire proprement , pas avec un SEGFAULT ou un assert.
C'est exactement le cas. Votre programme de traitement de texte n'a pas assez d'espace pour le prochain caractère donc il plante sans possibilité de sauvegarder ? Ce n'est pas une solution.
Et l'excuse du programme non-critique n'en est pas une. Si un utilisateur décide d'utiliser votre programme, alors c'est un programme critique.
Le C est un langage qui impose cette gestion de mémoire. Si vous ne vous sentez pas de la faire, alors le C n'est pas pour vous.
# C
Posté par devnewton 🍺 (site web personnel) . Évalué à 5.
Ca fait longtemps que je n'ai pas fait de C sans ++, mais est-ce qu'il n'y pas une lib simple pour manipuler des chaînes de caractères facilement?
Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.
# Je pense que tu confonds les cas où c'est nécessaire
Posté par Renault (site web personnel) . Évalué à 0.
Je suis d'accord avec le fait de vérifier les retours de malloc/realloc, mais uniquement quand la taille demandée est grande.
Car oui, si tu demandes une plage de mémoire trop grande, tu peux ne pas l'obtenir sans que le programme fonctionne mal à côté car il reste de la mémoire disponible.
Mais quand tu demandes quelques octets en mémoires, typiquement un petit tableau et qu'on te le refuse, on peut admettre sans problème qu'un tel cas n'arrive uniquement quand la machine est à genoux et qu'elle swappe à mort ou pire qu'elle est en train de planter lamentablement. Pour un petit programme de merde, ça ne vaut pas le coup de traiter un tel cas (qui n'arrive que rarement et la situation est en général dramatique en telle circonstance…).
Bref, allouer un gros bloc ou un petit ne demande pas une gestion de la mémoire de même finesse, car les conséquences sur l'état du système à ce moment là sont très différents.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par Etienne Bagnoud (site web personnel) . Évalué à 10.
Il n'y a pas de finesse dans la gestion de mémoire. Soit on en fait, soit on en fait pas. Ne pas en faire c'est du mauvais travail : c'est ce genre de programme qui plante quand on fait un aperçu avant impression et qui nous font jurer et traiter le programmeur de tous les noms qui nous passent par la tête.
Et tu ne peux rien admettre sur l'état de la machine en te basant sur le retour d'un malloc de 3 octets. La seule chose qu'on peut juger, c'est la qualité du travail du programmeur : bonne ou mauvaise.
"It was a bright cold day in April, and the clocks were striking thirteen" - Georges Orwell
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par pasBill pasGates . Évalué à 10.
La plupart des APIs ont la possibilite d'echouer, pour diverses raisons (mauvais parametres, rates reseau, etc…)
Faire :
ca prend quoi ? 5-10 secondes de plus, bref, rien de serieux niveau effort, et ca a l'avantage de permettre une gestion propre, avec un utilisateur qui ne voit pas ses softs exploser sans savoir pourquoi.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par Aeris (site web personnel) . Évalué à 1.
Le problème, c'est justement comme tu le dis si bien, la PLUPART des appels retourne des codes de retour en C, quasiment jamais vérifié, et que tu devrais toi aussi retourner du coup un code d'erreur.
Ton code, ça va bien quand t'as 1 appel (et encore, le goto, c'est mal…). Mais je fais quoi quand j'empile 3, 4, 10, 42 appels ?
Tes 5-10s se transforment en 5-10min puis 5-10j puis 5-10mois, puis… Et faut aussi inclure le coût de la maintenance et des évolutions futures.
C'est moche, c'est inmaintenable, c'est soumis à une palanqué de possibilité de bugs, ça fait du code spaghetti, ça fait qu'on perd à chaque fois la possibilité de retourner des valeurs non code d'erreur et donc qu'on doit se taper des passages par référence partout…
Et on ne sait généralement pas quoi faire sinon faire arrêter le programme de manière très moche dès qu'un truc retourne un truc non nul.
On n'a aucune certitude que notre programme fonctionnera correctement sauf à écrire du code considéré comme ignoble par tout développeur standard.
Et au final tout le monde s'en contre-fou de ces codes de retour, conduisant à des applis qui plantent/fuitent/buggent !
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par Renault (site web personnel) . Évalué à 4.
Je suis d'accord avec toi sur le reste de ton commentaire sauf :
Le goto est je pense une instruction intéressante uniquement pour la gestion d'erreurs. C'est justement plus lisible et propre que de tout faire à l'aide de if/else et compagnie… Là encore, c'est à utiliser avec parcimonie.
Donc comme je disais plus haut, vérifier ce type de retour ne me semble pas intéressant pour une application qui a un but très peu critique et surtout dans les cas où s'il y a une erreur c'est que ton système est de toute façon à la rue et ne pourra pas fermer le reste proprement…
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par ymorin . Évalué à 8.
Ah bah, oui, ton exemple contient déjà plusieurs erreurs !
Le principe d´un appel à une fonction est que, soit elle réussit, et dans ce cas toutes les réservations (allocation mémoire, descripteurs de fichiers, sockets réseau, verrous, etc … ) qu´elle a à faire sont faites, soit aucune n´est faite. Donc, en sortie de
foo()
, soit tout est fait, soit rien n´est fait. Donc pas la peine de nettoyerfoo()
si ça échoue. Ditto pourbar()
. Par contre, sibar()
échoue, c´est quefoo()
a réussi, donc il faut libérerfoo()
.Si une fonction comme
foo()
doit effectuer plusieurs réservations, et que cette fonction retourne un code d´erreur, comment savoir la raison de l´échec ? Il fait beaucoup plus sens de dépiler les réservations dans la fonction elle-même en cas d´erreur. La fonction de nettoyageundo_foo()
est pertinente uniquement pour libérer après usage, pas en cas d´erreur.Par exemple, cette séquence me semble plus correcte :
Cette séquence respecte le même principe décrit ci-dessus : soit toutes les réservations (
foo
etbar
) sont effectuées, soit aucune ne l´est.Dans ce cas, le
goto
est vraiment très utile. C´est un des rares cas où ça l´est.C´est d´ailleurs, il me semble, le schéma utilisé dans le noyau Linux pour les traitements d´erreurs, comme dans beaucoup d´autres logiciels.
Hop,
Moi.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par Aeris (site web personnel) . Évalué à 1.
Ça ne change pas grand chose au final, ce genre de code est juste ignoble et devrait être catapulté en orbite à coup de bombes nucléaires (pour paraphraser Linus).
Sur un bout de code à la con, on voit bien que pour 2 lignes utiles qui devraient conduire à une complexité cyclomatique de 1 et dont le pseudo-code serait
on en arrive à une 15aine de lignes de code et une complexité de 4.
Sur un soft d'une complexité standard, je te laisse imaginer ce que ça donne…
Idem en maintenance/évolution, alors que sur le pseudo-code on n'aurait qu'à ajouter une ligne entre foo() et bar(), en C on va devoir toucher aussi à toute la partie nettoyage/gestion d'erreur, aussi bien dans notre code que dans le code appelant. Idéal pour semer des régressions aux 4 coins de l'appli…
Au niveau développement, l'utilisateur d'une API n'a aussi aucune garantie de gérer toutes les erreurs possibles et imaginables que peut lui retourner une fonction, sauf à aller lire la doc (généralement fausse et/ou incomplète) voire pire, carrément le code source…
Et même s'il prend la peine d'aller lire la doc, il ne sait généralement pas quoi faire en cas d'erreur, sinon se mettre à remonter à l'appelant toutes les erreurs de sa couche basse.
« Eh les gars, on est en train de recoder les check-exceptions là ! »
Par extension, tout code en C DEVRAIT être codé selon ce principe, sauf à conduire aux différents problèmes soulevés PAR UN UTILISATEUR FINAL, c'est donc carrément le langage C qui devrait être mis en orbite !
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par pasBill pasGates . Évalué à 9.
on en arrive à une 15aine de lignes de code et une complexité de 4.
Sur un soft d'une complexité standard, je te laisse imaginer ce que ça donne…
La realite est que cette methode est utilisee dans certains des plus gros projets software existants (Windows et Office au hasard ainsi que le kernel Linux) et que ca marche tres bien.
Idem en maintenance/évolution, alors que sur le pseudo-code on n'aurait qu'à ajouter une ligne entre foo() et bar(), en C on va devoir toucher aussi à toute la partie nettoyage/gestion d'erreur, aussi bien dans notre code que dans le code appelant. Idéal pour semer des régressions aux 4 coins de l'appli…
Et pourquoi tu aurais besoin de changer le code appelant ? Tout ce que le code appelant a besoin de savoir est que ca a rate, il peut aller de maniere plus granulaire pour peut-etre re-essayer, logger un message plus precis, … mais le simple fait de savoir que la fonction a echoue est suffisant, bref, un switch-case avec un 'default' fait le boulot ou un if (ret==success) else {} aussi.
Au niveau développement, l'utilisateur d'une API n'a aussi aucune garantie de gérer toutes les erreurs possibles et imaginables que peut lui retourner une fonction, sauf à aller lire la doc (généralement fausse et/ou incomplète) voire pire, carrément le code source…
La beaute est justement que c'est totalement optionel. Le minimum a faire est voir que la fonction a reussie (tu compares au code de reussite), ensuite si tu veux gerer specifiquement qqe erreurs ou toutes les traiter de la meme maniere, c'est ton choix, mais tu sauras que la fonction a echoue:
Sinon, ta phrase "l'utilisateur d'une API n'a aussi aucune garantie de gérer toutes les erreurs possibles et imaginables que peut lui retourner une fonction, sauf à aller lire la doc (généralement fausse et/ou incomplète)" est franchement horrible, oh mon dieu, l'utilisateur doit lire la doc ? Grande nouvelle, si il ne l'a lit pas, mieux vaut qu'il n'ecrive pas de code !
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par Aeris (site web personnel) . Évalué à -6. Dernière modification le 09 septembre 2012 à 21:14.
C'est pas parce que tout le monde fait comme ça qu'il faut faire comme ça…
Sinon, ça veut dire que Windows est meilleur que Linux ?
En temps que développeur d'une application, tu es utilisateur des API de plus bas niveau.
Je ne parlais pas de l'utilisateur final, d'ailleurs ma phrase commence bien par « Au niveau développement ».
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par pasBill pasGates . Évalué à 9.
C'est pas parce que tout le monde fait comme ça qu'il faut faire comme ça…
Je serais ouvert a ecouter tes experiences sur le sujet relativement aux gros projets software et a leur success / qualite. Si tu as mieux, n'hesites pas.
Sinon, ça veut dire que Windows est meilleur que Linux ?
Vu que le kernel le fait aussi, comme je l'ai dit, non, ca veut dire que les 2 font comme il faut.
En temps que développeur d'une application, tu es utilisateur des API de plus bas niveau.
J'ai bien compris, et devines quoi, le developpeur doit lire la doc des APIs aussi, histoire de comprendre quels effets de bord ils peuvent avoir, dans quel cas ils peuvent se rater, etc…
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par Aeris (site web personnel) . Évalué à -5.
On va commencer par la fin, ça sera plus logique.
J'ai aussi une info de dernière fraicheur pour toi : une doc n'est jamais à jour, toujours obsolète, toujours incohérente, toujours incomplète, voire même inexistante.
Celui qui te dira le contraire est un fou.
Si tu persistes encore à maintenir le contraire, prend n'importe quelle doc de ton choix et prouve-moi qu'elle est non seulement correcte, mais en plus exhaustive de tout ce qui peut se passer via n'importe quel cas d'utilisation.
La seule et unique chose qui peut décrire sans omission le comportement d'un code-source, c'est ce code-source lui-même, et lui seul.
Tout ce qu'un développeur peut te dire au sujet de ce code, et en particulier la doc, est tout autant soumis aux bugs du développeur que le code lui-même.
Un développeur peut très bien ne pas s'apercevoir d'un effet de bord de son code, et ne le documentera donc pas.
Je développe des softs toute la journée, et la qualité logicielle est justement une de mes préoccupations principale.
Et pour te résumer ma position de manière succinte : le C et consort, c'est mal; le duck-typing, c'est mal; le non-typé, c'est mal.
Pour le cas qui nous concerne, les check-exceptions sont pour moi le meilleur moyen de résoudre le problème qui nous inquiète.
Si on regarde le problème initial :
- Dans 99% des cas, on ne sait pas quelles erreurs peuvent arriver pour chaque ligne du programme. Cf supra, la doc ne peut pas aider sur ce point;
- Dans 99% des cas, on ne sait pas gérer une erreur au niveau où l'on est, et on devra la propager au niveau supérieur, jusqu'à ce que quelqu'un sache la gérer;
- Dans les cas les plus graves (null pointer exception, out of memory, buffer overflow…), on sait pertinemment que rien ne sert de tenter le moindre rétablissement, on est mort, autant s'arrêter.
Plutôt que d'avoir du code ignoble et inmaintenable à écrire sur chaque ligne et pour chaque erreur possible et/ou avoir à écrire la propagation supérieure et/ou avoir à gérer l'arrêt du programme, et ce sur chaque méthode, chaque appel de méthode et chaque programme, ça devrait être au compilateur de faire ce travail.
D'autant plus qu'il est très laborieux à faire, très difficile à faire écrire correctement et soumis à la bonne volonté du développeur (et de son planning/ses délais/ses finances !).
Si on prend les exceptions à la Java, on règle d'un coup tous les problèmes :
- Chaque méthode déclarant toutes les exceptions qu'elle peut lever, aucun risque d'en oublier une seule;
- Si une erreur peut arriver, alors on a que 2 choix, soit on sait la traiter et on la traite, soit on ne sait pas et on la laisse remonter, en déclarant que notre propre méthode peut échouer;
- Dans les cas les plus graves, le système lève une exception non-contrôlée, qui remontera toute seule toute la chaîne d'appel et arrêtera le programme.
C'est le compilateur qui fera tout le travail ingrat, proprement et sûrement, de manière mutualisée, évitant au développeur de réinventer la roue (carrée, de préférence…).
Voilà comment faire les choses proprement : déléguer au compilateur le maximum possible de vérification.
Un compilateur ne fera pas d'erreur, un développeur en fera obligatoirement une.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par pasBill pasGates . Évalué à 10.
J'ai aussi une info de dernière fraicheur pour toi : une doc n'est jamais à jour, toujours obsolète, toujours incohérente, toujours incomplète, voire même inexistante.
Celui qui te dira le contraire est un fou.
Si tu persistes encore à maintenir le contraire, prend n'importe quelle doc de ton choix et prouve-moi qu'elle est non seulement correcte, mais en plus exhaustive de tout ce qui peut se passer via n'importe quel cas d'utilisation.
Ah ben voila, quelle logique ! Une doc peut avoir des erreurs, donc en toute logique : il ne faut jamais utiliser la doc !
Tu sais qu'un compilateur peut avoir des bugs, pourquoi tu en utilises un alors ?
Tout ce qu'un développeur peut te dire au sujet de ce code, et en particulier la doc, est tout autant soumis aux bugs du développeur que le code lui-même.
Un développeur peut très bien ne pas s'apercevoir d'un effet de bord de son code, et ne le documentera donc pas.
Super, ca ne change absolument RIEN au fait que la doc est utile, qu'elle donne plein d'informations correctes, et qu'elle evite de faire des erreurs.
Je développe des softs toute la journée, et la qualité logicielle est justement une de mes préoccupations principale.
Et pour te résumer ma position de manière succinte : le C et consort, c'est mal; le duck-typing, c'est mal; le non-typé, c'est mal.
C'est pas ce que je t'ai demande, je t'ai demande en quoi je devrais suivre tes dires plutot que ceux des devs du kernel Linux ou de chez Microsoft. Tu as quoi comme experience de gros developpements de qualite et a succes qu'ils n'ont pas ?
Pour le cas qui nous concerne, les check-exceptions sont pour moi le meilleur moyen de résoudre le problème qui nous inquiète.
Si on regarde le problème initial :
- Dans 99% des cas, on ne sait pas quelles erreurs peuvent arriver pour chaque ligne du programme. Cf supra, la doc ne peut pas aider sur ce point;
- Dans 99% des cas, on ne sait pas gérer une erreur au niveau où l'on est, et on devra la propager au niveau supérieur, jusqu'à ce que quelqu'un sache la gérer;
- Dans les cas les plus graves (null pointer exception, out of memory, buffer overflow…), on sait pertinemment que rien ne sert de tenter le moindre rétablissement, on est mort, autant s'arrêter.
Tu as faux sur toute la ligne. Je dis bien, toute la ligne, et j'ai des millions de lignes de code presentes sur 1 milliard de machines qui le prouvent. Je t'ai meme presente plus haut pourquoi il n'y a pas besoin de connaitre toutes les erreurs pour pouvoir les gerer.
Plutôt que d'avoir du code ignoble et inmaintenable à écrire sur chaque ligne et pour chaque erreur possible et/ou avoir à écrire la propagation supérieure et/ou avoir à gérer l'arrêt du programme, et ce sur chaque méthode, chaque appel de méthode et chaque programme, ça devrait être au compilateur de faire ce travail.
Ah ben oui, parce que le compilateur est sense savoir comment tu veux gerer une erreur ? Le compilateur est meme sense savoir ce qu'est une erreur ? Serieux, tu developpes vraiment professionellement ?
Si on prend les exceptions à la Java, on règle d'un coup tous les problèmes :
- Chaque méthode déclarant toutes les exceptions qu'elle peut lever, aucun risque d'en oublier une seule;
- Si une erreur peut arriver, alors on a que 2 choix, soit on sait la traiter et on la traite, soit on ne sait pas et on la laisse remonter, en déclarant que notre propre méthode peut échouer;
- Dans les cas les plus graves, le système lève une exception non-contrôlée, qui remontera toute seule toute la chaîne d'appel et arrêtera le programme.
C'est le compilateur qui fera tout le travail ingrat, proprement et sûrement, de manière mutualisée, évitant au développeur de réinventer la roue (carrée, de préférence…).
Vraiment ? Tu connais les problemes du traitement en exceptions ? Sont cout en performance, la difficulte d'eviter des leaks et autres quand l'exception se produit apres que tu aies alloue des ressources, pris des locks, etc… ?
Le systeme par exception n'a pas vraiment plus d'avantages ni d'inconvenients, c'est une autre approche simplement.
Ton 'truc' ou les erreurs que tu ne connais pas sont remontees c'est exactement ce que je t'ai montre plus haut en C. Mon code fait exactement la meme chose : une erreur inconnue est remontee aussi.
Et non le compilo ne fait pas tout le travail ingrat, le compilo ne sait pas liberer des ressources prises avant l'exception, il ne sait pas liberer un lock, etc…
Voilà comment faire les choses proprement : déléguer au compilateur le maximum possible de vérification.
Un compilateur ne fera pas d'erreur, un développeur en fera obligatoirement une.
Un compilateur peut faire des erreurs (et oui, ca existe), mais en plus, un compilateur ne sait pas gerer les erreurs, du tout.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par gst . Évalué à 3.
pour un coup je vais me faire l' "avocat du diable":
J'aurais même dit plus : cette méthode (ou ce type de méthode plus exactement parce que perso habituellement je préfère renvoyer le code d'erreur que de passer par errno), aussi bien en C mais aussi dans d'autres langages (mais peut-être plus en C,.. certes), est non seulement utilisée dans certains des plus gros projets logiciels, mais elle peut voire devrait l'être tout le temps (ou sinon des variantes avec gestion des exceptions par exemple) !
Faut lire sa parenthèse jusqu'au bout.. et je rajouterais, au niveau Linux, qu'il n'y a pas que dans le noyau qu'elle est utilisée, hooo que non.
Lui non plus il ne parlait pas de l'utilisateur final. Il parlait de l'utilisateur d'API (ou dit plus justement de bibliothèques de fonctions).
(quand bien même l'utilisateur final est aussi tenu de lire la doc de l'application qu'il utilise s'il veut savoir ce qu'elle fait et comment il faut s'y prendre).
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par Axioplase ıɥs∀ (site web personnel) . Évalué à 3.
Ben, utilise des macros pour enrober tout ça !
Ça me parait être une solution acceptable, si ce n'est pas LA méthode à utiliser…
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par Philippe F (site web personnel) . Évalué à 4.
Une macro ne pourra jamais remplacer un traitement intelligent d'une erreur. Le fait est que le traitement intelligent et correct d'une erreur est quelque chose de complexe (donc difficile à faire bien), coûteux en temps de dev, mais en même temps, indispensable dès qu'on arrive sur des gros logiciels.
Joel On Software y consacre un article pas trop mal d'ailleurs, que j'ai la flemme de retrouver.
Exception exhaustif à la java (je déclare tout ce que je lève), laxiste à la python (chaque ligne de code peut me balancer 24 exceptions différentes) ou code de libération en C avec des goto, aucune méthode n'est parfaite mais ce qui est sur, c'est que ça demande de l'investissement.
Suivant la taille du logiciel et son utilité, cet investissement est à mon sens pas toujours justifié.
Pour élargir le débat:
On peut aussi prendre le cas d'erreur du disque plein, ou du logiciel qui s'execute sur une partition sans les droits d'écritures. C'est des cas réels, compliqués à gérer, dans lesquels il faut informer l'utilisateur pour qu'il resolve intélligemment son problème. Dans ces deux cas, un simple crash est une mauvaise porte de sortie car l'utilisateur doit être informé de la situation pour pouvoir la régler.
Sinon le nouveau langage Go introduit un truc intéressant: des routines qui sont exécutées à la sortie de la fonction, dans l'ordre inverse où elles sont été déclarées. La logique est bien de mettre toute la libération des ressources dans ces routines, de façon à ce qu'elles soit libérées automatiquement en cas d'erreur.
Mais là encore, d'une part il faut être très rigoureux et d'autre part, il y a des fois où la libération de ressource est compliquée et même avec toute la structure du langage, on ne peut pas se passer d'une bonne prise de tête pour traiter avec honneur tous les cas tordus.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par Axioplase ıɥs∀ (site web personnel) . Évalué à 1.
Qui parle de remplacement intelligent ?
Il s'agit d'un remplacement systématique pour un cas précis : le realloc.
J'imagine que tous les reallocs ont une gestion d'erreur identique, et que c'est donc un devoir d'enrober ça dans une macro.
Si tu veux une gestion d'erreur sans te faire chier, tu changes de langage ; mais en aucun cas¹ tu ignores les erreurs ou tu les traites mal comme c'était le cas dans le code initialement cité.
¹ : sauf en Perl :)
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par ymorin . Évalué à 6.
En fait, non. Coupons un peu les cheveux en quatre, ça ne prend pas longtemps de s´amuser un peu. ;-)
Le raisonnement suivant suit la description de la page en anglais de Wikipedia, qui est plus fournie que la page en français sur la complexité cyclomatique.
Soit on considère que les deux
return
représentent deux points de sortie distincts, et on a le graphe suivant:Dans ce cas, la formule donne:
M = π - s + 2
, avec :π = 2
points de décision:foo?
etbar?
s = 2
points de sortie:X1
etX2
M = 2 - 2 + 2 = 2
Maintenant, si on considère les deux
return
comme étant un seul et unique point de sortie, le graphe devient:Et la formule donne :
π = 2
points de décision:foo?
etbar?
s = 1
point de sortie:X
M = 2 - 1 + 2 = 3
Donc, pas 4. Et les mouches ont mal, maintenant. ;-)
Hop,
Moi.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par Tonton Th (Mastodon) . Évalué à 4.
Il est évident que tu as oublié un autre cas, je te laisse chercher lequel.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par Grunt . Évalué à 1.
Quand on code pour Windows ?
THIS IS JUST A PLACEHOLDER. YOU SHOULD NEVER SEE THIS STRING.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par Chris K. . Évalué à 3.
Utilisation de ulimit ?
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par pasBill pasGates . Évalué à 1.
Ton app charge un fichier qui il se trouve est corrompu.
Le format contient quelque chose du genre [taille du tableau][tableau]
Ton app, pour charger le tableau va lire la taille et essayer de faire une allocation pour y stocker le tableau. Si la taille est corrompue (genre 0xFFFFFF0 ou autre truc enorme), ton soft va essayer de faire une allocation enorme, qui va rater meme si ta machine a encore plein de RAM dispo.
Est-ce que le soft devrait planter dans un cas pareil ? Non, il devrait voir qu'il n'arrive pas a lire le fichier et renvoyer une erreur a l'utilisateur mais si on lit certains ici, planter est la solution parait-il…
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par Renault (site web personnel) . Évalué à 0.
Est-ce que tu peux lire nos messages avant de les critiquer ?
Je n'ai jamais chargé un fichier avec malloc directement, j'aimerais que tu m'expliques le rapport entre allouer une zone mémoire et un fichier. Tu charges le fichier avec fopen normalement… Or je ne parle que de malloc ici.
Je parle d'une petite allocation de quelques octets, pas d'une matrice…
Comme je le disais, je parle de petite allocation. Car quand ta machine n'aura pas 10 octets à allouer, je peux te dire qu'elle doit être en situation très délicate et je doute que tu puisses faire quoique ce soit à ce moment là. Je ne parle pas d'allocations de plusieurs Mo ou plus qui peuvent échouer même en cas de RAM disponible.
Donc merci de mieux lire les messages et de comprendre la situation exposé au lieu de parler de situations dont je n'ai jamais rejeté l'utilité de vérifier le retour de malloc.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par pasBill pasGates . Évalué à 0.
Je n'ai jamais chargé un fichier avec malloc directement, j'aimerais que tu m'expliques le rapport entre allouer une zone mémoire et un fichier. Tu charges le fichier avec fopen normalement… Or je ne parle que de malloc ici.
Quand tu charges des donnees d'un fichier, tu les stockes ou ? En memoire le plus souvent, memoire qu'il faut allouer.
Je parle d'une petite allocation de quelques octets, pas d'une matrice…
Si tu sais d'avance qu'elle est petite oui, mais combien y a t'il de cas ou tu sais de maniere garantie qu'elle sera petite ? Combien sont de taille dynamique, ou la valeur vient de l'exterieur (Content-Length header d'une requete HTTP, taille de fichier, taille de tableau lue dans un fichier, valeur specifiee par l'utilisateur, etc…)
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par Renault (site web personnel) . Évalué à -1.
Je parle de cas où tu sais d'avance que c'est petit…
Arrête de faire semblant de ne pas comprendre mes propos…
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par pasBill pasGates . Évalué à 2.
Donc tu vas faire des checks seulement la moitie du temps dans ton code ? Tu vas te retrouver avec un code qui se comporte correctement la moitie du temps et qui explose l'autre moitie ? Tu vas te poser la question a chaque fois de ce que tu dois faire (verifier ou pas) plutot que simplement le faire systematiquement ?
Desole mais je ne vois vraiment pas ce que t'y gagne.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par YBoy360 (site web personnel) . Évalué à -1.
je comprends pas que tes commentaires soient autant surnotés, si tu codes comme un goret, et que tu mets directement ce que tu scannes en paramètre d'un malloc, un fichier corrompu fera craché ton programme même avec un malloc de 100 qui va réussir …
Il y a une différence entre traiter les erreurs et s'occuper d'un malloc qui retourne NULL. Quand tu scannes ton fichier, c'est là que tu gères l'erreur. Personne n'a sorti un exemple de code critique qui gérait spécifiquement le cas malloc retourne null.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par pasBill pasGates . Évalué à 3.
je comprends pas que tes commentaires soient autant surnotés, si tu codes comme un goret, et que tu mets directement ce que tu scannes en paramètre d'un malloc, un fichier corrompu fera craché ton programme même avec un malloc de 100 qui va réussir
Lorsque tu lis le fichier, et que le format te dit que le champs 'longueur' du tableau est de 4 octets, tu vas faire quoi comme test dessus pour savoir ou non si tu fais l'allocation ? C'est quoi la valeur maximale a accepter ? Tu pourrais bien verifier la taille du fichier d'abord, mais ca ne marche pas si c'est un tableau en sortie de decompression (tu ne connais pas le % de compression) ou qui vient d'un stream genre socket reseau…
Personne n'a sorti un exemple de code critique qui gérait spécifiquement le cas malloc retourne null.
Tu regardes l'OS que tu utilises et celui qui represente 90% du marche desktop, il en sont rempli.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par YBoy360 (site web personnel) . Évalué à -3.
donc on peut rien faire, il faut se fier au malloc pour savoir si ton fichier est corrompu… Regarde la structure de n'importe qu'elle système de fichier ou conteneur de données : les allocations se font par chunk. t'es libre de mapper directement tes données en mémoire depuis ton fichier, mais en cas de corruption, c'est pas le malloc qui échoue qui va te renseigner.
Dans l'OS que j'utilise, init/systemD/OpenSSL ne font rien (c.a.d. sortent ou attendent), sous Windows, bah tu me montres un exemple, j'ai pas accès au code.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par pasBill pasGates . Évalué à -1.
a) Oui, si dans ton fichier les seuls bytes modifies sont ceux du champs 'longueur', tu vas faire quoi pour verifier si c'est corrompu ? Comment tu vas faire pour savoir que ton tableau compresse il ne fait pas 0x101fe040 bytes une fois decompresse mais 0x001fe040 bytes ? Donnes moi la reponse si tu la connais…
Les allocations par chunk ca marche uniquement quand il est acceptable d'avoir les donnees en blocs separes, pour une image par exemple c'est souvent pas acceptable car ca complique enormement les manipulations sur les pixels sans parler du fait que ca empeche l'affichage…
Quand a mapper le fichier, si le contenu est compresse, ca te fait une belle jambe de le mapper…
b) Tu as montre toi meme le code d'init qui verifie la valeur de retour de malloc…
Pour Windows t'as pas besoin de sources, suffit de desassembler, regarder les instructions apres l'appel a malloc et voir qu'il fait une comparaison sur le registre eax qui est l'endroit ou la valeur de retour se trouve.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par YBoy360 (site web personnel) . Évalué à -2. Dernière modification le 11 septembre 2012 à 11:54.
A/ n'importe quoi : d'abord on parle de gérer par le code de sortie de malloc au cas ou on donne un paramètre corrompu … mais bon, on est pas à ça près, Je te prends au mot : si mon programme fais un écrasement mémoire, n'est-il pas plus judicieux de cracher ou l'erreur à lieu, plutôt que de se rattraper aux branches pour crasher 0.1 seconde plus tard (mais trop tard, tu sais plus ou est l'erreur à eu lieu)..
regarde la sérialisation de données (ou même, plus généralement n'importe qu'elle structure de données du type FS, compression ou autres), tu croies vraiment qu'il vaut mieux vérifier le retours d'un malloc plutôt que, à la lecture, vérifier les bornes avec une séquence d'octets magigues (du style B16B00B5)
B/ Je pense que tu es payé à me faire perdre mon temps (et aux autres), il n'y a pas de gestion d'erreur spécifique comme je le dis : ces programme ne font rien, RELIS.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par pasBill pasGates . Évalué à 1.
A) Mais de nouveau, si le parametre 'longueur' est corrompu, comment tu le sais quand tu lis ton fichier ?
Je t'ai donne un exemple, j'aimerais bien que tu me dises comment gerer cet exemple sans verifier la valeur de retour de malloc :
Tu dois allouer le buffer de destination pour decompresser le buffer, comment tu verifies si le champs longueur est OK ? La reponse est simple: tu ne peux pas, tout ce que tu peux faire est essayer d'allouer la taille demandee, retourner une erreur si ca ne marche pas, et t'assurer quand tu decompresses que tu ne depasses pas la taille allouee.
Mais si tu as un meilleur moyen, fais seulement, eclaires ma lanterne.
Tes bornes dans la serialisation elles vont servir a quoi ? A rien. Tu devrais voir a quoi ressemblent les failles de securite dans les formats :
a) Je modifies un champs pour y mettre une valeur inattendue
b) Je recalcules les checksums, etc… quand il y en a
c) J'envoies le fichier a un gars
d) A la lecture du fichier, le soft explose en vol
Le format du fichier ne peut pas etre de confiance car il vient de l'exterieur. La seule chose sur laquelle tu peux compter c'est ton propre code.
B) Tu as pose le code d'init toi-meme : Il fait un loop en verifiant si l'alloc reussit et si non il attend et re-essaie. Si on ecrivait cela comme nombre de gens ici le suggerent, init crasherait au 1er alloc qui rate.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par YBoy360 (site web personnel) . Évalué à -5. Dernière modification le 12 septembre 2012 à 11:00.
1/ Franchement, relis. Puis tu as 2 de QI ou quoi?? si la longueur est supérieur à la taille de ton fichier, tu te doutes qu'il y a un problème non? C'est pas moins c?? que de voir si ton malloc foire.
regarde du code sérialisant des objets, les conteneurs vidéo ou les spec de n'importe quel format de compression, ou la structure de n'importe qu'elle FS.
2/ J'ai l'impression que tu me cherches, Je perds encore une fois mon temps : il n'y a pas de gestion d'erreur spécifique à chaque malloc, regarde le code, TOUS les programmes sérieux/critiques ne font rien d'autre que : soit attendre, soit sortir. Pas de message d'erreur inutile spécifique, pas de je désalloue un truc inutile pour pouvoir continuer, pas de : oui alors je vais revenir dans un état cohérent ou je ne sais quelle autre Microsofterie. Sous Linux, lorsque malloc retourne NULL, t'es même pas sûr de pouvoir faire un appel de fonction…
Maintenant regardes ce qu'il se passe dans le imalloc de init au cas ou un autre goret coderait comme toi : ça part en boucle infinie! Mais c'est pas génial ça!
Dans le monde réel te répondre c'est déjà un peu partir en boucle, en ayant l'impression de devenir un peu moins intelligent à chacun de tes messages.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par Shuba . Évalué à 2.
Si si c'est bien utilisé en pratique une gestion plus fine des erreurs malloc, par exemple dans le code d'inkscape
Il y a une macro SAFE_MALLOC qui fait un goto sur malloc_error si malloc renvoie NULL, et les fonctions qui appellent SAFE_MALLOC se chargent de libérer correctement la mémoire après le goto et de renvoyer une sortie d'erreur. Pas de crash envisagé donc, et même plutôt le contraire. Il s'agit du codee pour allouer de nouvelles courbes, il vaut mieux en effet ne pas crasher le soft simplement parce qu'une nouvelle courbe a été crée..
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par shbrol . Évalué à 2.
Exemple donnée plus haut : sur le disque, le fichier est compressé, et tu le lit directement via une bibliothèque qui fait la décompression a la volée. Cette bibliothèque ne te donne pas la taille réelle des données (souvent elle ne le peut pas). Donc la taille du fichier sur disque ne donne aucune information pertinente, elle sera probablement inférieure à la longueur lue, sans que ça pose de problème.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par YBoy360 (site web personnel) . Évalué à -2. Dernière modification le 12 septembre 2012 à 17:07.
Ok dans ce cas tu fais ce que j'ai dis plus haut. tu mets un marqueur après ton buffer compressé ou un checksum sur la taille.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par YBoy360 (site web personnel) . Évalué à -2.
je peux pas éditer le précédant commentaire (on peut ajouter un checksum mais pas de marqueur), mais en fait si la taille est de 100 pour les données non compressé, et que le fichier est corrompu et la taille devient 10, le malloc foire pas et tu as un bel écrasement lorsque tu vas écrire les données.
ça marche par accident de tester si le malloc est NULL dans ce cas.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par pasBill pasGates . Évalué à -2.
De nouveau non tu ne comprends rien, le checksum ou le marqueur ne changent rien
A) Si je veux t'attaquer, je peux t'envoyer un fichier avec un mauvais champs longueur et une checksum correcte. Mais c'est de toute facon pas important parce que si le format du fichier ne comprend pas de checksum ou de marqueur, ben voila, tu ne peux pas en rajouter, tout simplement car le format n'en a pas et tu aurais des lors un fichier non conforme.
b) Ecraser le buffer n'est pas le probleme
Rien ne t'empeche de traiter ca correctement, tu sais que tu as alloue 100'000 bytes, suffit de t'assurer que tu n'ecris pas plus de 100'000 bytes quand tu decompresses et retourner une erreur si il y a des donnees restantes.
Ici, avant meme de commencer a decompresser, ton malloc a de tres grandes chances de retourner NULL sur x86 et tu vas exploser immediatement si tu ne verifies pas la valeur de retour.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par pasBill pasGates . Évalué à -2.
1) Je te suggeres de rester poli, surtout que tu as faux sur toute la ligne, et on est plusieurs a te le dire.
2) Le code serialisant des objets, les conteneurs video, etc… ont tous ces problemes, je te suggeres d'aller regarder les patches pour failles de securite dans libjpeg / libpng etc… cf. http://blog.cr0.org/2009/05/write-once-own-everyone.html par exemple.
3) Il y en a , tu l'as montre toi meme : attendre est de la gestion d'erreur, tu verifies la valeur retournee et tu evites un crash. Quelqu'un d'autre t'as montre plus bas qu'Inkscape gerait cela aussi et plein d'autres softs font de meme
Maintenant regardes ce qu'il se passe dans le imalloc de init au cas ou un autre goret coderait comme toi : ça part en boucle infinie! Mais c'est pas génial ça!
Tu es un sacre clown quand meme, tu as vu ou que j'ecrirais un code qui part en boucle infinie ? Tu as deja vu mon code ?
La difference entre toi et moi c'est que moi mon code tourne sur un milliard de machines, y compris des serveurs surcharges, et la plupart du temps sans probleme. Toi tu es visiblement bon pour insulter les gens, et beaucoup moins pour comprendre comment programmer proprement.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par lasher . Évalué à 2.
c'est con, j'allais plussoyer, mais la fin de ton message fait que je fais comme avec ton interlocuteur … -1
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par gst . Évalué à 6.
Je dois avouer que je n'ai jamais autant pertinenté pbpg et que ça me fait tout drôle.. (pourtant dieu sait que dans d'autres type de journaux je serais un des premiers à l'inutiliser)
Il semblerait donc que ce journal, d'ordre purement technique, soit suivi par beaucoup de gens du domaine et que ces gens soient en majorité d'accord avec ce qu'il énonce à longueur de commentaires.
d'ailleurs si on pouvait cesser de le relancer ça me ferait plaisir (parce que lui il a bien compris qu'il était sur un bon filon de + et ne s'en prive pas - à raison dirais-je) ;)
Je dirais qu'en résumé, qu'un soft soit "critique" ou pas (un soft peut-être non critique pour son auteur mais être utilisé dans une chaîne/situation critique par certains utilisateurs), vérifier les codes d'erreur des fonctions (externes ou pas) qu'on appelle et agir un tant soit peu en conséquence (au minimum un 'perror("fonctionX") ; exit(1);', ou un truc équivalent) c'est ce qu'il faudrait toujours faire, amha.
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par fearan . Évalué à 1.
C'est tout la question de savoir jusqu'à quel point il faut vérifier les entrées (ou les sorties); à tout vouloir vérifier, dans tous les moindres détails (y compris ceux qui ne sont pas censés arriver), on arrive à un ratio de ligne vérifiant les entrés très largement supérieur aux lignes utile au calcul / traitement;
Cela peut se ressentir à l'exécution; tout comme une certaine lourdeur dans le code et sa maintenabilité; j'ai un collègue qui parle de GIGO, (garbage in, garbage out), pour gérer des entrée hors normes.
Pour prendre le cas de sqrt() (fonction externe), je ne vais pas m'amuser à vérifier son retour, à la rigueur je test le nombre que je met en entrée, mais à partir du moment où je lui file un nombre positif, je sais que j'aurais un truc correcte en sortie.
Par contre pour le coup du malloc/realloc, j'aurais tendance à vouloir vérifier le résultat, je suis particulièrement sensible à ce genre de blague (par contre j'utiliserai asprintf pour faire ce qui est demandé ;
mon préféré restera une fonction c récursive
avec un realloc dans foo; une pure merveille (de bug vicieux) ;)
Il ne faut pas décorner les boeufs avant d'avoir semé le vent
[^] # Re: Je pense que tu confonds les cas où c'est nécessaire
Posté par YBoy360 (site web personnel) . Évalué à -3.
ça veut dire quoi "beaucoup de gens du domaine", du domaine de quoi?
tu la trouves technique cette discussion au point qu'il faille ranger les intervenants dans un "domaine"?? pas moi. C'est un peu le b.a. ba.
Il ne s'agit pas de ne pas vérifier les erreurs, il s'agit de ne pas pondre du code pour agir spécifiquement au cas ou un malloc retourne null dans un OS moderne. Si tu saisies pas la différence?
# asprintf
Posté par serge_sans_paille (site web personnel) . Évalué à 3.
Si on est prêt à utiliser les extensions GNU,
asprintf
fait des merveilles. Sinon, il y a la gnulib qui apporte la couche de portabilité manquante et qui s'intègre merveilleusement bien à un build autotool.# criticité
Posté par neologix . Évalué à 10. Dernière modification le 09 septembre 2012 à 12:55.
Le OOM killer se tune par sysctl (overcommit_memory knobs) et /proc (oom_adj), pas à la compilation.
Et ce n'est pas un tour de magie, mais plutôt un hack ignoble (et compter dessus est stupide et non portable).
Tu sous-estime la complexité.
OK, tu testes ton pointeur, tu vois qu'il est NULL.
Mais pour faire les choses proprement, par exemple prévenir l'utilisateur, ou sauvegarder le fichier, et bien il faut de la mémoire.
Bah oui, créer un popup, ça demande de la mémoire. fprintf() utilise malloc(), tout comme fwrite() (oui je connais setvbuf()), etc. Quand tu y penses, sans malloc(), il ne te reste plus grand chose de disponible.
En fait c'est encore bien pire que ça, parce que sur OOM (je ne parle pas du cas où tu es limité par RLIMIT_DATA/RLIMIT_AS…), quasiment n'importe quel appel système peut foirer: fork() (vérifié sur OpenIndiana), write(), close()…
Donc la morale, c'est que tu ne pourras probablement pas sauvegarder ton fichier, et que tu risques de le corrompre si tu essaies.
C'est pour cela que dans les applications critiques, il faut utiliser des opérations atomiques, ou des transactions. Par exemple, ton traitement de texte bosse sur une copie du fichier, et ensuite fait un truc du style :
Au moins là tu es tranquille.
Par ce que tu vois le cas d'OOM, mais ton process peut se faire tuer par un SIGKILL ou n'importe quel autre signal, et dans ce cas là tu ne pourras pas catcher.
Mais ce genre de chose est complexe, et ce n'est probablement pas la peine de le faire sur un programme qui se content de te donner ta consommation en CPU/RAM/bande passante. Il n'a pas d'état, pas d'effets de bords, il peut crasher n'importe quand.
Par contre, mettre un assert() est pas mal pour éviter une éventuelle faille de sécurité due au déréférencement d'un pointeur NULL.
[^] # Re: criticité
Posté par pasBill pasGates . Évalué à 8.
Mais pour faire les choses proprement, par exemple prévenir l'utilisateur, ou sauvegarder le fichier, et bien il faut de la mémoire.
Bah oui, créer un popup, ça demande de la mémoire. fprintf() utilise malloc(), tout comme fwrite() (oui je connais setvbuf()), etc. Quand tu y penses, sans malloc(), il ne te reste plus grand chose de disponible.
Ca depend des cas, comme tout. Si ton soft voit son alloc echouer alors que tu es en train de charger un 14eme document, ben il abandonne et vide tout ce qu'il a alloue pour le 14eme, et il evite de tout crasher et forcer l'utilisateur a rouvrire les 13 premiers documents, c'est un avantage indeniable.
Bref, gerer les mallocs qui ratent, ca ne coute quasiment rien, et ca peut rapporter gros.
[^] # Re: criticité
Posté par Etienne Bagnoud (site web personnel) . Évalué à 8.
Dans les noyaux 2.4 on pouvait ne pas le compiler, dans les versions suivantes je ne sais pas.
C'est pour ça que tu vas allouer de la mémoire au lancement du programme pour avoir une "réserve" pour ce genre de cas. Si tu ne peux pas allouer un peu de mémoire, tu pioches dans ta réserve de secours pour gérer correctement l'erreur. Je ne sous-estime pas la complexité, je la reconnais mais la complexité n'est pas une excuse pour ne rien faire.
Tu en sais quoi qu'il pourrait crasher n'importe quoi ? Est-il utilisé dans un environnement plus complexe où un crash pourrait avoir des répercussion plus importantes ? Tu n'en sais rien, l'utilisateur part du principe que les programmeurs sont civilisés et qu'il peut utiliser le programme comme bon lui semble, y compris l'utiliser comme élément critique. Ou alors le programme indique clairement : "attention ce programme ne gère pas du tout la mémoire, il ne faut donc pas l'utiliser à part si vous n'en avez pas besoin" !
Sauf que ton assert est viré lors de la compilation en mode production. Donc utiliser un assert pour des questions de sécurité … je trouve ça bizarre. Mais on va me dire que toutes les possibilités existantes sont testées donc on peut utiliser un assert, c'est ça ?
Soyons sérieux, tu gueules si un appareil électrique grille à la première surtension. Pourtant il est prévu pour le réseau standard, donc pourquoi mettre un fusible ? Et finalement, c'est juste un gadget, pas un appareil critique, ce n'est pas grave s'il grille : il suffit de le ramener sous garantie. Sauf que ta maison à cramer à cause de ce raisonnement.
"It was a bright cold day in April, and the clocks were striking thirteen" - Georges Orwell
[^] # Re: criticité
Posté par neologix . Évalué à 0.
Bah voyons, montre-moi tu empêches fprintf() ou un appel système de retourner ENOMEM en cas de OOM, je suis curieux.
Tout les process peuvent crasher, à n'importe quel moment : OOM, signal, mémoire défectueuse…
Le seul critère est l'impact du crash : et dans ce cas, un logiciel non critique, sans persistance de données, sans effets de bords, l'impact est négligeable.
OK, donc explique-moi ce que tu pourrais faire de plus qu'un _exit() en cas de OOM sur ce programme précis (parce qu'on parle de celui-là, toute la question est de la pertinence d'une telle gestion en fonction de la criticité). Si ton programme ne peut plus fonctionner correctement, autant le terminer pour remonter l'erreur que de continuer et faire n'importe quoi, non (encore une fois, je parle de programmes non critiques) ?
OK, quand je vois ce genre de comparaisons, je me dis que ce n'est pas la peine de discuter…
Tu compares une installation électrique à un gadget GUI qui t'affiche ta consommation CPU.
Si tu ne fais pas la différence de criticité entre les deux, je ne peux rien pour toi…
Si en Java, OutOfMemoryError est une Error, et pas une checked exception, il y a une raison…
Par ailleurs, je ne vois toujours pas le lien entre realloc() et la fuite mémoire, tu devrais utiliser valgrind au lieu de patcher le code au petit bonheur la chance…
[^] # Re: criticité
Posté par Etienne Bagnoud (site web personnel) . Évalué à 7.
Dans certains cas, le code faisait plus ou moins
return realloc(ptr, /* ... */);
. La suite du code gère correctement le pointeur NULL, donc ça ne crash pas, mais on a plus de référence pour libérer la mémoire ensuite."It was a bright cold day in April, and the clocks were striking thirteen" - Georges Orwell
[^] # Re: criticité
Posté par neologix . Évalué à 2.
Encore une fois, je doute très fortement que ce soit la cause des fuites mémoire.
malloc()/realloc() qui retourne NULL, c'est très rare, il faut vraiment être en OOM ou atteindre RLIMIT_DATA/RLIMIT_AS.
Utilise "ltrace -e malloc,realloc" pour t'en convaincre…
Et surtout valgrind…
[^] # Re: criticité
Posté par pasBill pasGates . Évalué à 1.
OK, donc explique-moi ce que tu pourrais faire de plus qu'un _exit() en cas de OOM sur ce programme précis (parce qu'on parle de celui-là, toute la question est de la pertinence d'une telle gestion en fonction de la criticité). Si ton programme ne peut plus fonctionner correctement, autant le terminer pour remonter l'erreur que de continuer et faire n'importe quoi, non (encore une fois, je parle de programmes non critiques) ?
Mais qui t'a dit qu'il ne peut plus fonctionner correctement ? Ce soft peut liberer des buffers qu'il detient et re-essayer, simplement abandoner le chargement du fichier qu'il etait en train de lire et continuer de travailler avec les autres documents qu'il a charge, etc…
[^] # Re: criticité
Posté par Renault (site web personnel) . Évalué à 1. Dernière modification le 09 septembre 2012 à 21:23.
On parle de logiciels non critiques et toi tu parles de fichier à sauvegarder tout ci tout ça.
Un logiciel comme LibreOffice est critique, car il manipule des données importantes, pouvant être grosses et avoir de la valeur pour l'utilisateur.
Un jeu de cartes sur ordinateur ou un applet qui affiche la météo n'en sont pas par exemple, s'ils crashent cela n'a aucune conséquence. Par conséquent la manière de gérer les choses n'est pas la même. Il me semble important de savoir gérer les situations suivants l'environnement et le contexte. En entreprise tu ne coderas pas les applications de la même manière si tu sais que tu vas vendre un code à plusieurs clients qu'à un seul, s'il y a du support ou non après, si c'est fait pour un avion ou seulement pour un ordinateur personnel, etc.
Un malloc qui échoue pour distribuer un octet n'aura pas le même impact et la machine ne sera pas dans le même état qu'un malloc pour des dizaines de Mo.
[^] # Re: criticité
Posté par pasBill pasGates . Évalué à 4.
Parce que tu sais d'habitude comment ton soft va etre utilise ?
Prends un soft de conversion basique : il prend un fichier en entree, et en ressort un autre.
Il ne fait que lire le fichier d'entree, resultat il ne risque pas d'endommager quoi que ce soit.
Tu te mets a coder comme tu dis, sans verifier si les allocations reussissent, etc… parce que bon, si l'utilisateur il voit son soft exploser en vol c'est pas grave apres tout, il n'a rien perdu hein.
Le probleme est quand ton utilisateur se met a faire des batchs de ta commande par divers moyen (shell, etc…), il n'aura aucune idee qu'il y a eu un probleme dans le fichier 468, il ne saura pas ce qui a cause le probleme, etc…
Idem pour le gars qui se decide de creer un service de conversion sur le web, il va passer des fichiers venant de l'exterieur a ton soft de conversion, et devine quoi, ton petit soft pas important va devenir une faille de securite sur le systeme de l'utilisateur car tu ne verifies pas que tes allocations fonctionnent.
Alors si tu parles d'un petit gadget de 100 lignes oui qui affiche l'heure, oui ca revient a taper sur un moustique avec un martinet, mais vu l'effort supplementaire que ca demande qui est minimal mieux vaut le faire parce que c'est une habitude a prendre plus qu'autre chose. Le jour ou tu te mettras a coder un truc plus gros, tu feras la chose correctement de maniere instinctive plutot qu'inserer des erreurs a tout bout de champs a cause de tes mauvaises habitudes precedentes.
[^] # Re: criticité
Posté par Lizzie Crowdagger (site web personnel) . Évalué à 0.
«Parce que tu sais d'habitude comment ton soft va etre utilise ?»
Pour l'essentiel des softs «graphiques» j'ai quand même l'impression que c'est le cas (OK, ça peut être forké ou modifié, mais a priori si tu fais un jeu de démineur ou un coincoin< il y a peu de chances que ça devienne un soft hyper critique).
Pis bon, oui, dans l'absolu il vaut mieux gérer des erreurs que ne pas les gérer, sauf que dans la vraie vie j'ai l'impression que pour un soft pas prévu pour ça et qui devient un truc super critique tel qu'utilisé par d'autres gens, t'en as aussi une pelletée qui sont téléchargés par deux pelés.
(Je trouve que l'argument de «l'habitude à prendre» est plus pertinent, même si je trouve que c'est pas forcément applicable si tu codes quelques petits trucs de temps en temps sans avoir pour ambition de «coder un truc plus gros un jour».)
[^] # Re: criticité
Posté par neologix . Évalué à 7.
Pour info, voici le code d'openssh [1] :
[1] http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/
[^] # Re: criticité
Posté par Etienne Bagnoud (site web personnel) . Évalué à 5.
Oui, il gère en quittant le programme. Ça a du sens pour eux de le gérer ainsi. Mais ils ne continuent en laissant arrivé le SIGFAULT.
Je pense qu'un serveur de base de données ne va pas quitter brutalement s'il n'y a pas assez de mémoire pour retourner le résultat d'une requête. Tout comme une éditeur de texte ne va pas le faire.
Dans tous les cas, les programmes gèrent l'absence de mémoire. Ils comptent pas sur quelque chose d'externe qui pourrait les sauver.
La "criticité" va déterminer comment tu gères ton retour NULL. Programme de moindre importance peut se contenter de faire "exit", un programme comme systemd va tenter l'impossible pour ne pas faire "exit" (enfin je l'espère).
En C,
*NULL='a';
est indéfini par la norme C89/99. Donc ça peut SEGFAULT, mais ça peut être aussi valable (x86 en mode réel) ou alors certains microcontrolleur. Mais indéfini ça veut dire que le comportement ne peut pas être prédit !"It was a bright cold day in April, and the clocks were striking thirteen" - Georges Orwell
[^] # Re: criticité
Posté par YBoy360 (site web personnel) . Évalué à 0.
tu devrais essayer d'utiliser Word/Wordpad/MySQL quand tu as plus de mémoire.
sinon, pour "init" (le bon vieux, bien critique)
# Pourquoi malloc() puis realloc() ?
Posté par Jérôme Flesch (site web personnel) . Évalué à 3. Dernière modification le 09 septembre 2012 à 13:10.
Ton code d'exemple me chiffonne un peu: Pourquoi faire un malloc() puis un realloc() ?
Ça peut être fait avec 2 appels à snprintf() et un seul malloc(). En effet, si tu spécifie une taille de buffer de 0 au 1er snprintf(), logiquement, il ne devrait pas chercher à remplir le buffer passé en argument. Par contre tu devrais quand même récupérer la taille finale de la chaine. Ça me semble plus court, plus lisible, et plus optimisé.
Au final, ça donne un truc dans ce genre là:
[^] # Re: Pourquoi malloc() puis realloc() ?
Posté par Etienne Bagnoud (site web personnel) . Évalué à 3.
Avant de proposer une optimisation, lire la page de manuel n'est pas du luxe :
Donc 2 spécifications se contredisent sur cette fonction. Autant ne pas faire cette optimisation. De plus dans ton optimisation, tu appelles toujours deux fois snprintf et une fois malloc, alors qu'avec une valeur de départ bien choisie, tu peux être dans le cas où tu appelles le plus souvent une fois snprintf et une fois malloc et uniquement dans les cas spéciaux (voir jamais) appeler deux fois snprintf.
"It was a bright cold day in April, and the clocks were striking thirteen" - Georges Orwell
[^] # Re: Pourquoi malloc() puis realloc() ?
Posté par Jérôme Flesch (site web personnel) . Évalué à 3. Dernière modification le 09 septembre 2012 à 13:36.
1) Pour ma défense, la dernière fois que j'ai lu la page man de snprintf(), c'était au boulot, sous FreeBSD. Et le man FreeBSD ne mentionne pas SUSv2.
2) On a la norme SUSv2 qui dit "comportement indéterminé" (autrement, la libc fait ce qu'elle veut), et C99 dit "comportement déterminé" --> Quel est le problème avec supposer que la libc respecte la norme C99 ?
3) Pour ne pas être souvent dans "les cas spéciaux", il faut choisir un DEFAULT_SIZE assez, voir trop, large. Avec mon implémentation, la question ne se pose pas.
[^] # Re: Pourquoi malloc() puis realloc() ?
Posté par Maxime (site web personnel) . Évalué à 1.
Et au moins tu utilises une coding style lisible (espaces).
[^] # Re: Pourquoi malloc() puis realloc() ?
Posté par Jérôme Flesch (site web personnel) . Évalué à 1.
Arh, effectivement, je retire ce que j'ai dit au point 2. Il faut vraiment que je prenne le temps de lire plus soigneusement le man …
[^] # Re: Pourquoi malloc() puis realloc() ?
Posté par Jérôme Flesch (site web personnel) . Évalué à 3.
Retournement de situation:
Le man parle de SUSv2 alors que SUSv3 (aka POSIX.1-2001) et SUSv4 (aka POSIX.1-2008) existent. Et il semblerait que SUSv3 corrige le comportement de snprintf() pour le rendre C99-compliant:
# fuites mémoire
Posté par zerkman (site web personnel) . Évalué à 6. Dernière modification le 09 septembre 2012 à 14:05.
Ce n'est pas ce qui a été dit dans les commentaires de ton journal précédent.
Dans ce journal, tu prétends que ne pas tester les valeurs de retour de realloc provoque des fuites mémoire. A cela on te répond que non, puisque le processus ne survit de toutes façons pas si realloc échoue. Personne, je crois, n'a dit qu'il n'y avait pas de problème à ne pas tester les codes d'erreur de realloc (partir du principe que le programme part en SEGFAULT ce n'est pas dire qu'il n'y a pas de problème).
Tu prends même pour exemple une application gnome qui donne l'heure et d'autres infos système, et qui occupe la mémoire de façon importante. Si tu trouves la cause de l'occupation mémoire importante, je suis prêt à parier que ce n'est pas à cause d'un realloc dont on aurait ignoré la valeur de retour.
Une fuite mémoire c'est un tampon qui n'est jamais libéré alors qu'il aurait dû. Aucun rapport avec la choucroute ici.
[^] # Re: fuites mémoire
Posté par Etienne Bagnoud (site web personnel) . Évalué à 6.
Dans l'exemple que j'ai pris, sur la quinzaine d'erreur, dans le journal oui en effet. On peut dire que l'exemple en question était mauvais. Mais il y'a des cas où le code fait, pratiquement, ça :
et la suite gère un pointeur NULL. Dans ce cas, on a des fuite, vu que si realloc échoue, ptr est perdu à jamais.
Ensuite il y a des commentaires qui indiquent que contrôler les valeurs de retour de malloc/realloc est une perte de temps si le programme n'est pas assez critique. Ici ou dans l'autre journal. Ce qui est une aberration.
"It was a bright cold day in April, and the clocks were striking thirteen" - Georges Orwell
[^] # Re: fuites mémoire
Posté par zerkman (site web personnel) . Évalué à 2.
là ok, c'est le cas typique d'appli codée avec les pieds, ou pire, par des gens qui sont passés de Java à C et qui pour qui c'est du code parfaitement acceptable.
Je suis d'accord aussi. Diffuser du code qui ne fait pas de test d'erreurs devrait être considéré comme une très mauvaise idée. Si je considère pouvoir le faire chez moi, je m'interdis de diffuser publiquement du code qui fait ça. Donc du coup je fais du code propre systématiquement, au cas où.
L'argument de la criticité du code a déjà été discuté, et est loin de faire l'unanimité, ce qui est une bonne chose.
[^] # Re: fuites mémoire
Posté par Lizzie Crowdagger (site web personnel) . Évalué à 3.
«Je suis d'accord aussi. Diffuser du code qui ne fait pas de test d'erreurs devrait être considéré comme une très mauvaise idée. Si je considère pouvoir le faire chez moi, je m'interdis de diffuser publiquement du code qui fait ça. Donc du coup je fais du code propre systématiquement, au cas où.»
Si t'as les capacités de le faire, il vaut mieux le faire, OK mais d'un autre côté je trouve ça bien que des gens qui sont pas forcément capables de coder proprement (parce que c'est pas leur métier, qu'ils font juste un petit prog pour eux et se disent «ça servira peut-être à quelqu'un» et ont pas forcément l'energie d'apprendre la gestion d'erreurs de tel langage pour ça alors que ça marche) diffusent quand même ce qu'ils font, ne serait-ce que parce que des fois sur des trucs spécifiques c'est pratique d'avoir un petit outil qui servira potentiellement à deux personnes, même s'il est codé de manière hyper gruik, ne fait pas de correction d'erreur et plante de temps en temps.
[^] # Re: fuites mémoire
Posté par gnumdk (site web personnel) . Évalué à 2.
Je suis relativement d'accord, pour une truc à la con comme conky, franchement, on s'en fou, c'est pas un service critique…
La question est de savoir si il existe des services critiques codés en mode on s'en fou de la gestion des erreurs, j'espère bien que non .
[^] # Re: fuites mémoire
Posté par groumly . Évalué à 6.
Apres avoir lu ce fil, je vais sourire quand quelqu'un resortira l'argument sur la superiorite technique du libre et la stabilite legendaire du monde linux face a windows qui fait qu'a planter.
Linuxfr, le portail francais du logiciel libre et du neo nazisme.
# Simplification
Posté par Taurre . Évalué à 3.
Salut,
Je suis tout à fait d'accord sur le fait que les retours des fonctions d'allocation doivent être vérifiés. Au programmeur de décidé ensuite de l'action à effectuer en cas d'échec de ces dernières (un simple appel à exit pour un programme non critique par exemple).
Je notes simplement, comme Jérôme Flesch, que le code pourrait se passer d'un appel à malloc en utilisant simplement une variable de type char pour le premier appel à snprintf. Je remarque également que le retour de snprintf n'est pas vérifié (il peut être négatif ou nul) et que l'espace pour le caractère de fin de chaîne a été oublié.
Je suis d'accord sur ce point également, mais dans ce cas ci il s'agit d'un comportement non conforme au standard donc bon, je ne me retournerais personnellement pas dessus.
[^] # Re: Simplification
Posté par Jérôme Flesch (site web personnel) . Évalué à 0.
C'est conforme au standard C99 et SUSv2 ne contredit pas C99. SUSv2 dit juste qu'il n'offre pas de garantie dans ce cas, mais C99, lui, en offre.
(Pour ceux qui auraient raté épisode précédent, vu que c'est dans un autre thread, on parle de l'appel à snprintf(NULL, 0, …))
[^] # Re: Simplification
Posté par Jérôme Flesch (site web personnel) . Évalué à 0. Dernière modification le 09 septembre 2012 à 23:41.
Hm, ok, je note, ne plus lire le man et poster après 23h … les 2 standards se contredisent bel et bien …
hmbref, +1 pour ta solution donc :)
# Et printf ?
Posté par Philippe F (site web personnel) . Évalué à 6.
Puisqu'on est dans de la vraie reflexion de fond, posons les bonnes questions.
Qui vérifie les valeurs de retour de printf ? Et que faire en cas d'échec de printf ?
En tout cas, le débat n'est pas inintéressant. Je rejoins PbPg sur le fait que faire un bon test et chaînage d'erreur est la base d'une bonne bibliothèque. Je le fais quand je développe des libs à usage externe. Et une des règles de base est d'ailleurs : ne jamais retourner true quand une fonction réussi. On retourne toujours 0 quand ça réussit de façon à pouvoir glisser dans le futur tous les cas d'erreurs nouveaux.
Ça suppose quand même d'avoir une grande base de code d'erreurs unifiés pour permettre la propagation assez simplement. Ça veut dire aussi qu'il faut convertir tous les cas d'erreurs des libs externes qu'on appelle. Ça peut vite être assez lourd. Mais très bien fait, ça permet d'identifier assez précisément une erreur quand une application se crash. C'est la différence entre "ah tiens mon application carte à puce crash" et "ah tiens, le driver propriétaire de mon lecteur de carte à puce n'arrive pas à faire de changement de vitesse de communication avec ma carte à puce", ce qui est tout de suite beaucoup plus utile.
Cela étant dit, je tends plutôt vers une approche à la Zenitram: au quotidien, très peu de soft que j'écris est critique, on est soit dans la ligne de commande, soit dans l'appli graphique à deux balles. Et donc la gestion de ce genre d'erreur me passe un peu au dessus. Côté diagnostic, j'ai plus tendance à m'appuyer sur du logging bien foutu que sur l'erreur elle-même retournée. Il est vrai que je fais beaucoup de python ou une bonne stacktrace est quand même sacrément claire!
[^] # Re: Et printf ?
Posté par pasBill pasGates . Évalué à 3.
Ça suppose quand même d'avoir une grande base de code d'erreurs unifiés pour permettre la propagation assez simplement
Ca existe deja, suffit de reutiliser(Win32 en contient des centaines par exemple).
Ça veut dire aussi qu'il faut convertir tous les cas d'erreurs des libs externes qu'on appelle.
Oui et non, ca depend de la granularite des erreurs que tu veux gerer, et sous Windows au moins c'est souvent transparent vu qu'il y a "une" liste standard d'erreurs.
Cela étant dit, je tends plutôt vers une approche à la Zenitram: au quotidien, très peu de soft que j'écris est critique, on est soit dans la ligne de commande, soit dans l'appli graphique à deux balles. Et donc la gestion de ce genre d'erreur me passe un peu au dessus.
Je veux bien que ce soit pas critique dans ce cas, mais faire cela :
a) T'empeche de reutiliser ce que tu as ecrit dans quelque chose de plus important
b) Te donne des mauvaises habitudes je dirais, qui rende l'ecriture de code "critique" plus risque
Mon point de vue est que ces checks sont de toute facon tres rapide a ajouter(il y a quasiment toujours des chemins d'erreurs pour d'autres raisons, suffit donc de les reutiliser) et qu'au final cela coute tellement peu a faire qu'il vaut mieux le faire systematiquement.
[^] # Re: Et printf ?
Posté par Philippe F (site web personnel) . Évalué à 2.
C'est vrai et c'est assez appréciable sous Win32. Mais c'est pas utilisable quand on fait un soft portable. Et c'est difficilement utilisable quand tu fais un soft qui assemble plusieurs bibliothèques ensemble. Voici donc deux cas, où la gestion d'erreur propre demande de développer son propre gestionnaire de codes d'erreur et de message.
Oui pour un truc très simple, si l'application est structurée de cette façon. Non si tu veux faire un truc un minimum intelligent et détaillé, pour par exemple proposer une solution à quelques erreurs courantes.
L'exemple de la partition où tu peux pas écrire mais où tu veux sauver ton document est intéressant à traiter…
[^] # Re: Et printf ?
Posté par lasher . Évalué à 4.
Sauf que ce que je ne pige pas dans tout ça, c'est qu'au bout d'un moment, tout le monde finit par développer (ou réutiliser) un ensemble de fonctions utilitaires qui justement enrobent les fonctions utiles (donc
malloc
,free
, avec une fonction genrefatal
oucrash
qui logge un message d'erreur et sort…).Si ton soft n'est pas critique, il suffit de les utiliser pour que ça plante « proprement ». Sinon, de toute manière il faudra développer ou réutiliser du code plus robuste. Je pense que ce qu'Étienne veut faire passer comme message, c'est qu'il n'est quand même pas sorcier de faire des fonctions « wrapper » pour ce qui est utilisé souvent, et ainsi faire une gestion minimale des erreurs.
J'ai raté quelque chose ?
# Fuite mémoire
Posté par Buf (Mastodon) . Évalué à 6.
Tu as l'air de penser que c'est les corrections sur les realloc qui ont réduit l'occupation mémoire. J'en doute très fortement.
Tes corrections, ça permet d'éviter une fuite sur une situation extrêmement rare. Un malloc ou realloc qui échoue, c'est un cas réellement exceptionnel. Si tu veux tester ça, remplacer les reallocs par un petit wrapper qui log les cas où ça retourne NULL. Je suis prêt à parier que tu n'en verras aucun.
À mon avis, le gain de mémoire que tu constates, ça doit venir d'options de compilations différentes.
[^] # Re: Fuite mémoire
Posté par YBoy360 (site web personnel) . Évalué à 1.
D'un coté il y a la bonne méthode, de l'autre … l'autre.
il me serait pas venu à l'idée de mesurer un delta en consommation mémoire en regardant VmPeak et encore moins VmSize, sur de si petites modifications (je dis pas que c'est dénué de sens, c'est juste comme mesurer le niveau d'un bassin pour compter les poissons qu'il y a dedans alors qu'on les connait tous par leur nom).
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.