Supposons que cela fasse partie de mon code:
int foo()
{
char *p, *q ;
if((p = malloc(BUFSIZ)) == NULL) {
return ERROR_CODE;
}
if((q = malloc(BUFSIZ)) == NULL) {
free(p)
return ERROR_CODE;
}
/* Do some other work... */
free(p);
free(q);
}
Puisqu'il est possible que le premier malloc
réussisse mais que le second échoue, j'utilise free(p)
dans le deuxième "gestionnaire d'erreurs". Mais que faire s'il y a plus de malloc
et si je veux modifier le code (ajuster leurs commandes, ajouter ou supprimer des malloc
)?
Je sais qu'en C++, il y a des choses comme RAII et sauf exception, etc. Mais en général, quelle est la bonne façon de gérer l'échec de malloc
en C? (peut-être en utilisant goto
?)
Votre code est correct, mais pour beaucoup de variables, je préfère:
int
foo()
{
char *p = NULL;
char *q = NULL;
int ret = 0;
if (NULL == (p = malloc(BUFSIZ)))
{
ret = ERROR_CODE;
goto error;
}
// possibly do something here
if (NULL == (q = malloc(BUFSIZ)))
{
ret = ERROR_CODE;
goto error;
}
// insert similar repetitions
// hopefully do something here
error:
free (p);
free (q);
return ret;
}
Notez que libérer NULL
est défini comme un no-op.
Cela évite n
niveaux de retrait pour les variables n
. Vous pouvez nettoyer les descripteurs de fichiers, etc. de la même manière (bien que vous deviez mettre une condition autour de la close()
).
Maintenant, si vous savez que vous pouvez les allouer tous en même temps, dasblinkenlight a une bonne réponse, mais voici une autre façon:
int
foo()
{
int ret = 0;
char *p = malloc(BUFSIZ);
char *q = malloc(BUFSIZ);
char *r = malloc(BUFSIZ);
if (!p || !q || !r)
{
ret = ERROR_CODE;
goto exit;
}
// do something
exit:
free(p);
free(q);
free(r);
return ret;
}
Dernière possibilité: si vous voulez réellement quitter le programme en cas d'échec de malloc
, envisagez d'utiliser l'option M_CHECK_ACTION
De mallopt
. Ceci fait vérifier les défauts de malloc()
et appelle abort()
, imprimant éventuellement un message utile.
Depuis la page de manuel:
NOM
mallopt
- définir les paramètres d'allocation de mémoireSYNOPSIS
#include <malloc.h> int mallopt(int param, int value);
DESCRIPTION
La fonction
mallopt()
ajuste les paramètres qui contrôlent le comportement des fonctions d'allocation de mémoire (voirmalloc(3)
). L'argumentparam
spécifie le paramètre à modifier etvalue
spécifie la nouvelle valeur de ce paramètre.Les valeurs suivantes peuvent être spécifiées pour
param
:
M_CHECK_ACTION
La définition de ce paramètre contrôle la réponse de la glibc lorsque divers types d'erreurs de programmation sont détectés (par exemple, libérer le même pointeur deux fois). Les 3 bits les moins significatifs (2, 1 et 0) de la valeur affectée à ce paramètre déterminent le comportement de la glibc, comme suit:
Bit: Si ce bit est défini, imprimez un message sur une ligne sur
stderr
qui fournit des détails sur l'erreur. Le message commence par la chaîne"*** glibc detected ***"
, Suivie du nom du programme, du nom de la fonction d'allocation de mémoire dans laquelle l'erreur a été détectée, d'une brève description de l'erreur et de l'adresse mémoire où l'erreur a été détectée .Bit 1: Si ce bit est défini, après avoir imprimé tout message d'erreur spécifié par le bit 0, le programme se termine en appelant
abort(3)
. Dans les versions glibc depuis 2.4, si le bit 0 est également défini, entre l'impression du message d'erreur et l'abandon, le programme imprime également une trace de pile à la manière debacktrace(3)
, et imprime le mappage de mémoire du processus dans le style de/proc/[pid]/maps
(voirproc(5)
).Bit 2: (depuis la glibc 2.4) Ce bit n'a d'effet que si le bit 0 est également activé. Si ce bit est défini, le message sur une ligne décrivant l'erreur est simplifié pour ne contenir que le nom de la fonction où l'erreur a été détectée et la brève description de l'erreur.
Puisqu'il est parfaitement OK de passer NULL
à free()
, vous pouvez allouer tout ce dont vous avez besoin en "ligne droite", tout vérifier en une seule fois, puis tout libérer une fois que vous êtes que vous ayez effectué ou non un travail:
char *p = malloc(BUFSIZ);
char *q = malloc(BUFSIZ);
char *r = malloc(BUFSIZ);
if (p && q && r) {
/* Do some other work... */
}
free(p);
free(q);
free(r);
Cela fonctionne tant qu'il n'y a pas de dépendances intermédiaires, c'est-à-dire que vous n'avez pas de structures avec des dépendances à plusieurs niveaux. Lorsque vous le faites, c'est une bonne idée de définir une fonction pour libérer une telle structure, sans supposer que tous les blocs de mémoire sont non -NULL
.
Pour un grand nombre d'allocations, j'investirais du temps dans la création d'un gestionnaire de mémoire qui garde la trace des allocations. De cette façon, vous n'avez jamais à vous soucier des fuites, que la fonction réussisse ou non.
L'idée générale est de créer un wrapper pour malloc
qui enregistre les allocations réussies, puis les libère sur demande. Pour libérer de la mémoire, vous passez simplement une taille spéciale à la fonction wrapper. En utilisant une taille de 0
pour libérer de la mémoire est approprié si vous savez qu'aucune de vos allocations réelles ne sera destinée à 0
blocs de taille. Sinon, vous pouvez utiliser ~0ULL
comme taille de demande de libération.
Voici un exemple simple qui permet jusqu'à 100 allocations entre les libres.
#define FREE_ALL_MEM 0
void *getmem( size_t size )
{
static void *blocks[100];
static int count = 0;
// special size is a request to free all memory blocks
if ( size == FREE_ALL_MEM )
{
for ( int i = 0; i < count; i++ )
free( blocks[i] );
count = 0;
return NULL;
}
// using a linked list of blocks would allow an unlimited number of blocks
// or we could use an array that can be expanded with 'realloc'
// but for this example, we have a fixed size array
if ( count == 100 )
return NULL;
// allocate some memory, and save the pointer in the array
void *result = malloc( size );
if ( result )
blocks[count++] = result;
return result;
}
int foo( void )
{
char *p, *q;
if ( (p = getmem(BUFSIZ)) == NULL ) {
return ERROR_CODE;
}
if ( (q = getmem(BUFSIZ)) == NULL ) {
getmem( FREE_ALL_MEM );
return ERROR_CODE;
}
/* Do some other work... */
getmem( FREE_ALL_MEM );
return SUCCESS_CODE;
}
SI vous vous attendez à allouer un grand nombre d'articles, cela Can devient désordonné. Essayez d'éviter l'approche "goto". Pas à cause de l'ancienne éthique du "goto is bad", mais parce que de cette façon, il peut y avoir de la folie et des fuites de mémoire.
C'est un peu exagéré pour un petit nombre de malloc, mais vous pouvez envisager quelque chose comme cette approche:
void free_mem(void **ptrs, size_t len)
{
for (size_t i = 0; i < len; ++i)
{
free(ptrs[i]);
ptrs[i] = NULL;
}
}
int foo(...)
{
void *to_be_freed[N];
int next_ptr = 0;
for (size_t i = 0; i < N; ++i) to_be_freed[i] = NULL;
p = malloc(..);
if (!p)
{
free_mem(to_be_freed,N);
return ERROR_CODE;
}
to_be_freed[next_ptr++] = p;
// Wash, rinse, repeat, with other mallocs
free_mem(to_be_freed,N)
return SUCCESS;
}
En réalité, vous pouvez probablement envelopper malloc avec quelque chose qui suit cela. Mettez le tableau et la taille du tableau dans une structure et passez-le avec la taille d'allocation souhaitée.
c'est une question d'habitude, mais je préfère:
int returnFlag = FAILURE;
if ((p = malloc...) != NULL)
{
if ((q = malloc..) != NULL)
{
// do some work
returnFlag = SUCCESS; // success only if it is actually success
free(q);
}
free(p);
}
return returnFlag; // all other variants are failure
Je pense que la première réponse est le but le plus général car elle peut être utilisée pour des erreurs autres que celles causées par malloc. Cependant, je supprimerais les gotos et utiliserais une boucle de passage unique comme ça.
int foo()
{
char *p = NULL;
char *q = NULL;
int ret = 0;
do {
if (NULL == (p = malloc(BUFSIZ)))
{
ret = ERROR_CODE;
break;
}
// possibly do something here
if (NULL == (q = malloc(BUFSIZ)))
{
ret = ERROR_CODE;
break;
}
// insert similar repetitions
// hopefully do something here
} while(0);
free (p);
free (q);
return ret;
}