Que faire après un échec d'alloc?
Du genre:
Code:
1 2 3 4
| Toto * tots = malloc (count * sizeof(Toto) ;
if (totos == NULL) {
// et là, on fait quoi?
} |
Au départ, j'ai mis des assert (totos != NULL), juste pour noter les endroits où je devrai plus tard faire quelque chose de plus sensé. C'est l'heure de m'occupper de ça, mais j'ai aucune idée de ce qui serait mieux. Au moins, l'assertion dit clairement quel est le problème et surtout où. A première vue, il me semble qu'un utilisateur ne peut rien faire de mieux que nous rapporter les faits et le message d'erreur. Alors, quoi?
Dans un langage même légèrement de plus haut niveau, on ne s'occupe tout simplement pas de ces choses-là. Ca ne se produit pas (?). Ce qui signifie que si, par ex en python, un objet ne peut pas être alloué, l'utilisateur se retrouve face à un message d'erreur C légèrement reformulé pas le runtime python (ce sera une MemoryError de python si mes souvenirs sont corrects) : ça lui fait une belle jambe, c'est tout aussi "impertinent" et ça le laisse tout autant impuissant.
J'aimerais bien trouver une stratégie sensée générale qui puisse s'appliquer à tous les cas où un défaut nous échappe, comme les relations avec le système de fichier ou les entrées-sorties en général.
note: Je ne parle pas ici des défauts qui font en fait (ou devraient faire) partie de la logique de l'application, par exemple un fichier de config n'est pas là : il doit là y avoir une voie de secours.
Denis
qu'est-ce que 'assert' fait de mal?
Il é méchan ? ;)
Ce n'est pas que je ne veux pas ne pas l'utiliser. Au contraire, chercher comment faire avec des macros qui vont bien me donnera l'occase de découvrir d'autres aspects du langage C.
Mais je voudrais comprendre pourquoi je ne dois pas l'utiliser. Si afficher un message d'erreur pertinent sur stderr est le mieux qu'on peut faire, alors assert ne fait-il cela justement très bien?
Le seul pb qui me vient à l'esprit, c'est que assert est peut-être réservé par convention aux tests. Dans ce cas, c'est gênant de le réutiliser ailleurs. Sinon, peut-être qu'il n'est pas compilé en mode release (il me semble avoir lu un truc comme ça qq part)?
Donc, à moins qu'il y ait mieux, je compte faire la chose suivante: chercher la def de la macro assert (c'est bien une macro?) dans la stdlib, la comprendre, la reproduire, et appleler le résultat verify...
Merci à vous,
Denis
exemple de fonction de crash
En attendant d'avoir une idée plus claire sur ce problème de besoin de mémoire durant un traitement d'erreur (qui peut justement être un défaut de mémoire...), j'ai exploré un peut les moyens std que C nous offre pour crasher proprement ;), et la forme que ça pourrait avoir (sorry pour l'anglais, mais des utilisateurs potentiels n'ont aucune raison d'être particulièrement francophones, alors pas trop le choix, désolé).
Alors, j'ai une fonction crash() générale qui prend comme params un message, la position dans le source (à partir de macros std, dont __func__ de C99), et un "contact" (à partir de macros elles-mêmes à définir). Et une version de crash spécifique pour les binz mémoire: verify_alloc, qui appelle crash.
Voilà ce qu'un crash de verify_alloc donne:
Code:
1 2 3 4 5 6
| *** error ********************************************************
System could not provide necessary memory.
position: function test_VERIFY_ALLOC (in toolkit.c, line 118)
PLease report this failure, copying 2 lines above, to:
author of 'testapp', Foo Z. Bar (foo.bar@baz.org)
****************************************************************** |
Pour vous donner des idées, voilà comment c'est construit (ça utilise un ou deux trucs de mon toolkit dont vous conprendrez ce qu'ils sont je crois):
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| /* crash !!!
*/
#define AUTHOR "Foo Z. Bar"
#define EMAIL "foo.bar@baz.org"
#define APPLICATION "testapp"
#define CONTACT format("author of '%s', %s (%s)", \
APPLICATION, AUTHOR, EMAIL)
#define SOURCE_POS format("position: function %s (in %s, line %d)", \
__func__, __FILE__, __LINE__)
void crash (string message, string source_pos, string contact) {
line () ;
puts ("*** error ********************************************************");
puts (message) ;
puts (source_pos) ;
puts ("PLease report this failure, copying 2 lines above, to:") ;
printf (" %s\n", contact) ;
puts ("******************************************************************");
exit (EXIT_FAILURE) ;
}
// Note: verify_alloc is a macro because it uses macro SOURCE_POS which
// itself uses macros (__FILE__, etc) relative to current position in
// source file. The alternative is to have verify_alloc directly take
// SOURCE_POS as arg, but since it is used on each alloc...
const string ALLOC_ERROR_MESSAGE =
"System could not provide necessary memory." ;
#define verify_alloc(p) if ((p) == NULL) \
crash (ALLOC_ERROR_MESSAGE, SOURCE_POS, CONTACT) ; |
Le but est donc d'avoir:
* crash comme modèle général pour toutes les anomalies qu'on ne peut pas (ou veut pas) gérer soi-même,
* verify_alloc, exemple typique, utilisé après chaque alloc.
Tout commentaire bienvenu, autant sur la conception que l'implémentation.
(Ma première modif va être justement de réserver au démarrage genre 1 kb pour la construction du message d'erreur de crash, et le libérer au départ de crash.)
Merci,
Denis