Emballer des appels à malloc () / realloc ()… est-ce une bonne idée?

Pour une affectation, je dois allouer un tampon dynamic, en utilisant malloc() pour le tampon initial et realloc() pour développer ce tampon si nécessaire. Partout où j’utilise (re | m) alloc (), le code se présente comme suit:

 char *buffer = malloc(size); if (buffer == NULL) { perror(); exit(EXIT_FAILURE); } 

Le programme ne lit que les données d’un fichier et les sort, alors je pensais que quitter le programme quand (re | m) alloc échouait serait une bonne idée. Maintenant, la vraie question est:

Serait-il avantageux de terminer les appels, par exemple, comme ceci?

 void *Malloc(int size) { void *buffer = malloc(size); if (buffer == NULL) { perror(); exit(EXIT_FAILURE); } return buffer; } 

Ou est-ce une mauvaise idée?

Dois-je me préoccuper de détecter les erreurs de mémoire MOO (mémoire insuffisante) dans mon code C?

C’est ma réponse à une question similaire. En résumé, je suis favorable à la conception d’applications pour qu’elles récupèrent tout type de crash, puis traitent le manque de mémoire comme un motif de blocage.

C’est une mauvaise idée dans la forme présentée, car dans tout autre programme qu’un programme sortingvial écrit pour une tâche, vous voulez faire quelque chose de plus utile / plus gracieux que de renflouer. Alors mieux vaut ne pas prendre de mauvaises habitudes. Cela ne veut pas dire que les enveloppes d’allocation sont mauvaises en soi (la gestion centralisée des erreurs peut être une bonne chose), mais simplement une enveloppe dont vous ne vérifiez pas la valeur de retour (par exemple, qui ne retourne pas du tout échec) est une mauvaise idée, à moins d’avoir prévu un mécanisme permettant au code de s’intégrer à la logique de sauvetage.

Si vous souhaitez le faire sous la forme que vous avez présentée, je vous recommande vivement d’utiliser un nom plus clair que Malloc au lieu de malloc . Comme malloc_or_die . 🙂

Dans la plupart des cas, la tentative d’utilisation d’un pointeur null provoquera une interruption du programme assez tôt, ce qui facilitera le débogage, car vous obtiendrez un fichier de vidage de kernel agréable, que vous n’obtiendrez pas si vous appelez exit()

Le seul conseil que je donnerais est de déréférencer le pointeur renvoyé le plus tôt possible après l’avoir atsortingbué, ne fût-ce qu’à titre gratuit, afin que le core dump puisse vous mener directement à l’appel de malloc défectueux.

Il y a rarement beaucoup de choses que vous pouvez faire pour récupérer de votre mémoire épuisée, donc quitter est habituellement la bonne chose à faire. Mais faites-le de manière à faciliter la post-mortem.

Pour être clair, cependant, l’épuisement de la mémoire survient généralement longtemps après que le système d’exploitation a été paralysé par une activité de permutation de pages. Cette stratégie n’est vraiment utile que pour attraper des allocations ridicules, comme essayer de malloc (a_small_negative_number) à cause d’un bogue.

Dans votre cas, ça va. Rappelez-vous simplement de donner un message avec les raisons de la sortie prématurée, et il serait bien de spécifier le numéro de ligne. Quelque chose comme ça:

 void* malloc2(int size, int line_num){ void *buffer = malloc(size); if (buffer == NULL) { printf("ERROR: cannot alloc for line %d\n", line_num); perror(); exit(EXIT_FAILURE); } return buffer; }; #define Malloc(n) malloc2((n), __LINE__) 

EDIT: comme d’autres l’ont mentionné, ce n’est pas une bonne habitude pour un programmeur expérimenté, mais pour un débutant qui a des difficultés à garder une trace du déroulement du programme dans des cas “heureux”, c’est acceptable.

Les idées selon lesquelles “vérifier l’échec de malloc est inutile en raison d’un engagement excessif” ou que “le système d’exploitation sera déjà paralysé par l’échec de malloc ” sont sérieusement dépassées. Les systèmes d’exploitation robustes n’ont jamais surchargé la mémoire. De plus, ceux qui sont peu robustes (comme Linux) disposent désormais de moyens simples pour désactiver la surcapacité et éviter que le système d’exploitation ne soit endommagé par l’épuisement de la mémoire – tant que les applications ne s’épuisent pas. et brûler quand malloc échoue!

malloc peut échouer sur un système moderne pour de nombreuses raisons:

  • Ressources physiques insuffisantes pour instancier la mémoire.
  • Espace d’adressage virtuel épuisé, même lorsqu’il rest beaucoup de mémoire physique libre. Cela peut se produire facilement sur une machine 32 bits (ou dans un espace utilisateur 32 bits) avec plus de 4 Go de RAM + swap.
  • Fragmentation de la mémoire. Si vos modèles d’allocation sont très mauvais, vous pourriez vous retrouver avec 4 millions de segments de 16 octets espacés de manière égale entre 1 000 octets et incapable de satisfaire un appel malloc(1024) .

La façon dont vous traitez avec l’épuisement de la mémoire dépend de la nature de votre programme.

Certes, du sharepoint vue de la santé du système dans son ensemble, il est agréable que votre programme meure. Cela réduit le manque de ressources et peut permettre à d’autres applications de continuer à fonctionner. D’autre part, l’utilisateur sera très contrarié si cela implique de perdre des heures de travail à monter une vidéo, à taper un article, à rédiger un article de blog, à coder, etc. Sinon, il pourrait être heureux si son lecteur mp3 meurt subitement avec -memory signifie que leur disque cesse de battre et qu’ils peuvent revenir à leur traitement de texte et cliquer sur “enregistrer”.

En ce qui concerne la question initiale d’OP, je vous déconseille vivement d’écrire des wrappers Malloc qui meurent en cas d’échec ou d’écrire du code qui suppose simplement qu’il segfault immédiatement après l’utilisation du pointeur nul en cas d’échec de malloc . C’est une mauvaise habitude facile à prendre, et une fois que vous aurez écrit un code plein d’allocations non vérifiées, il sera impossible de le réutiliser ultérieurement dans un programme où la robustesse compte.

Une solution bien meilleure consiste simplement à renvoyer échec à la fonction appelante et à laisser la fonction appelante renvoyer échec à sa fonction appelante, etc., jusqu’à ce que vous reveniez au niveau main ou similaire, où vous pouvez écrire if (failure) exit(1); . De cette façon, le code est immédiatement réutilisable dans d’autres situations où vous pourriez vouloir vérifier les erreurs et prendre des mesures de restauration pour libérer de la mémoire, sauvegarder / dump des données précieuses sur le disque, etc.

Je pense que c’est une mauvaise idée, car tout d’abord vérifier le retour de malloc ne vous rapporte pas grand-chose sur les systèmes modernes et ensuite, cela vous donne une fausse sécurité, à savoir que lorsque vous utilisez un tel appel, toutes vos atsortingbutions vont bien.

(Je suppose que vous écrivez pour un environnement hébergé et non intégré, autonome.)

Les systèmes modernes avec un grand espace d’adressage virtuel ne renverront jamais (void*)0 depuis malloc ou realloc séparément, peut-être si les arguments sont faux. Vous rencontrerez beaucoup de problèmes, beaucoup plus tard, lorsque votre système commencera à échanger comme un fou ou même à manquer d’échange.

Alors non, ne vérifiez pas le retour de ces fonctions, cela n’a pas beaucoup de sens. Au lieu de cela, vérifiez les arguments de malloc rapport à 0 (et pour realloc si les deux sont realloc à 0 simultanément) avec une assertion, car le problème ne se situe pas dans malloc ou realloc mais dans la façon dont vous les appelez.