1
votes

Pourquoi malloc (0) provoque-t-il une fuite de mémoire majeure sous Windows?

Je viens de terminer un joyeux 4,5 heures de débogage d'une mauvaise fuite dans mon système.

Il s'avère que je faisais ceci:

params = allocate(sizeof(Something*) * num_params); 

qui à son tour essentiellement appelle malloc avec le premier argument transmis. Lorsque num_params vaut 0, il appellerait malloc(0).

En exécutant ceci en boucle, le programme occuperait très rapidement la plupart de la mémoire. Je l'ai corrigé en vérifiant d'abord si num_params == 0 , et si c'est le cas en évitant l'appel à allocate.

Je sais que le Standard dicte malloc (0) est défini par l'implémentation. Alors, comment cela est-il implémenté dans la bibliothèque d'exécution Windows 7 C, et pourquoi cela provoque-t-il une fuite?

Edit: Clarification de la question - pourquoi malloc (0 ) sous Windows allouent de la mémoire, et quelle est la logique qui détermine la quantité de mémoire allouée?


9 commentaires

Une fuite de mémoire n'est pas liée à malloc (0). Cela peut se produire lorsque vous ne libérez pas la mémoire allouée allouée avec malloc (0).


@VladfromMoscow Je comprends. Plus tard dans le code, j'ai vérifié if (num_params> 0) free_things (); . En supposant que malloc (0) n'alloue rien. Je suppose que la question serait de savoir pourquoi et comment malloc (0) alloue de la mémoire à partir du tas.


Mon hypothèse est que vous ne libérez pas la mémoire parce que vous pensez qu'aucune n'a été allouée (peut-être que vous stockez et vérifiez la taille par rapport à zéro). malloc (0) peut encore allouer de la mémoire que vous devez libérer: " Généralement, le pointeur fait référence à un bloc de mémoire de longueur nulle constitué entièrement de structures de contrôle. "


Notez que cela peut très bien être nul. Vous avez peut-être alloué de la mémoire d'une valeur de 0 octets de données + X octets de métadonnées du gestionnaire de mémoire. (Par exemple, les métadonnées peuvent contenir les informations sur la taille des données ... zéro dans ce cas ^^)


Lorsque vous allouez de la mémoire, une certaine surcharge est impliquée. Ainsi, même l'allocation de 0 octets prend de la mémoire.


Pouvez-vous fournir un exemple reproductible minimal? Ce serait bien de pouvoir expérimenter cela - et de s'assurer que l'erreur n'est pas causée par un autre bogue sans rapport.


En bref, chaque fois que vous appelez malloc , vous devez toujours transmettre le pointeur renvoyé à free . Notez que free (NULL) est parfaitement valide, vous n'avez donc pas à vérifier cela.


Mais vous devez faire attention à realloc (0) ! Voir ceci aussi


OP dit "En supposant que malloc (0) n'alloue rien." C'est assez clair dans le page de manuel . Si la taille est 0, malloc alloue un élément de longueur nulle dans le tas et renvoie un pointeur valide vers cet élément.


4 Réponses :


2
votes

malloc (0) renvoie une nouvelle adresse valide car c'est l'option qu'il a choisie parmi celles autorisées par le standard C.

7.22.3 Fonctions de gestion de la mémoire (c'est moi qui souligne)

1 L'ordre et la contiguïté du stockage alloué par appels successifs aux fonctions align_alloc, calloc, malloc et realloc est non spécifié. Le pointeur renvoyé si l'allocation réussit est convenablement aligné pour pouvoir être affecté à un pointeur vers n'importe quel type de l'objet avec une exigence d'alignement fondamentale et ensuite utilisé pour accéder à un tel objet ou à un tableau de tels objets dans l'espace alloué (jusqu'à ce que l'espace soit explicitement désalloué). La vie de un objet alloué s'étend de l'allocation jusqu'au désallocation. Chacune de ces attributions renvoie un pointeur vers un objet disjoint de tout autre objet . Le pointeur a renvoyé des points au début (adresse d'octet le plus bas) de l'espace alloué. Si la l'espace ne peut pas être alloué, un pointeur nul est renvoyé. Si la taille de l'espace demandé est nul, le comportement est défini par l'implémentation: soit un pointeur nul est renvoyé, soit le le comportement est comme si la taille était une valeur différente de zéro, sauf que le Le pointeur renvoyé ne doit pas être utilisé pour accéder à un objet.

L'implémentation sous windows choisit le second comportement. Il doit donc effectuer une allocation pour s'assurer que l'exigence apparaît immédiatement avant. Chaque pointeur valide renvoyé par une fonction d'allocation doit être disjoint de tout autre pointeur renvoyé par une fonction d'allocation.


Lecture pertinente:


6 commentaires

Quelle est la motivation pour mettre en œuvre le deuxième comportement, me dépasse.


@TonyTannous - ¯ \ _ (ツ) _ / ¯ Mais cela pourrait constituer une bonne question s'il n'en existe pas déjà


@TonyTannous, que malloc () n'échoue pas brusquement semble être une motivation raisonnable pour choisir la deuxième option. C'est également le choix de la glibc.


Je ne suis pas en désaccord avec cette réponse, mais je ne sais pas si elle constitue une réponse complète à la question. La question semble être de dire que malloc (0) leur alloue des quantités déraisonnablement grandes de mémoire, et elle a été modifiée pour demander spécifiquement la taille de l'allocation.


@john Bollinger intéressant. Je suis allé à la documentation de malloc.c dans la glibc. Taille minimale allouée: 4 octets ptrs: 16 octets (dont 4 overhead) 8 octets ptrs: 24/32 octets (y compris 4/8 overhead) et Même une requête pour zéro octet (c'est-à-dire, malloc (0)) renvoie un pointeur vers quelque chose de la taille minimum allouable


Ok, @TonyTannous. Cela vérifie mon affirmation selon laquelle le malloc de Glibc gère un argument de 0 en allouant plutôt qu'en échouant. Mais vous auriez pu simplement consulter le manuel pour cela, et les détails de l'espace alloué par malloc () Glibc ne sont pas pertinents pour la situation de l'OP.



2
votes

Certains systèmes renvoient simplement NULL sans rien allouer. Mais il est parfaitement légitime pour malloc d'allouer un bloc de mémoire de 0 octet et de renvoyer un pointeur vers cette mémoire. Ce bloc doit être libéré comme tout autre bloc alloué par malloc .

Lorsque vous allouez de la mémoire, il y a une certaine surcharge impliquée. Ainsi, même allouer 0 octet prend de la mémoire.

De plus, les systèmes peuvent surallouer ou avoir des restrictions d'alignement qui peuvent rendre une partie de la mémoire inutilisable après chaque allocation. Cela peut arriver ou non ici.


Dans les commentaires, vous avez mentionné que vous faites quelque chose du genre:

if (params) {
    ...free elements of params...
    free(params);
}

Au lieu de cela, vous auriez dû faire ce qui suit:

if (num_params > 0) {
    ...free elements of params...
    free(params);
}


0 commentaires

0
votes

De l'homme page:

Si la taille est 0, malloc alloue un élément de longueur nulle dans le tas et renvoie un pointeur valide vers cet élément

Et, avec la vérification valgrind pour le petit programme:

HEAP SUMMARY:
==11887==     in use at exit: 0 bytes in 0 blocks
==11887==   total heap usage: 1 allocs, 1 frees, 0 bytes allocated
==11887== 
==11887== All heap blocks were freed -- no leaks are possible

Nous avons le journal:

HEAP SUMMARY:
==11874==     in use at exit: 0 bytes in 1 blocks
==11874==   total heap usage: 1 allocs, 0 frees, 0 bytes allocated
==11874== 
==11874== 0 bytes in 1 blocks are definitely lost in loss record 1 of 1
==11874==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

Mais avec la fonction gratuite :

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int *p = malloc(0);

    if(!p) {
        printf("%p\n", p);
    }
    //free(p);
    return 0;
}


2 commentaires

C'est Linux, pas Windows. Veuillez consulter mon commentaire .


Désolé pour ça. J'ajoute le lien pour la description de malloc pour la fenêtre



0
votes

Comme d'autres l'ont mentionné, malloc (0) est défini par l'implémentation, un pointeur nul est renvoyé ou le comportement est comme si la taille était une valeur différente de zéro. Il s'agit clairement de la deuxième option et citant la documentation de Microsoft pour malloc

Si la taille est 0, malloc alloue un élément de longueur nulle dans le tas et renvoie un pointeur valide vers cet élément.

Ne spécifie pas les frais généraux.


Pour nous faire une idée, nous pouvons examiner l'implémentation de la glibc pour malloc.c

Même une demande de zéro octet (c'est-à-dire malloc (0)) renvoie un pointeur à quelque chose de la taille minimum allouable


Taille minimale allouée:
ptrs 4 octets: 16 octets (dont 4 overhead)
ptrs 8 octets: 24/32 octets (y compris, 4/8 overhead)


0 commentaires