web-dev-qa-db-fra.com

Pourquoi malloc initialise-t-il les valeurs à 0 dans gcc?

C'est peut-être différent d'une plateforme à l'autre, mais

quand je compile en utilisant gcc et exécute le code ci-dessous, j'obtiens 0 à chaque fois dans mon ubuntu 11.10.

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

int main()
{
    double *a = (double*) malloc(sizeof(double)*100)
    printf("%f", *a);
}

Pourquoi le malloc se comporte-t-il ainsi malgré le calloc?

Cela ne signifie-t-il pas qu'il y a une surcharge de performance indésirable juste pour initialiser les valeurs à 0 même si vous ne voulez pas que ce soit parfois?


EDIT: Oh, mon exemple précédent n'était pas l'initiazling, mais il est arrivé d'utiliser un bloc "frais".

Ce que je cherchais précisément, c'est pourquoi il l'initialise lorsqu'il alloue un gros bloc:

int main()
{
    int *a = (int*) malloc(sizeof(int)*200000);
    a[10] = 3;
    printf("%d", *(a+10));

    free(a);

    a = (double*) malloc(sizeof(double)*200000);
    printf("%d", *(a+10));
}

OUTPUT: 3
        0 (initialized)

Mais merci d'avoir souligné qu'il y a une raison de SÉCURITÉ lors du mallocage! (Je n'y ai jamais pensé). Bien sûr, il doit s'initialiser à zéro lors de l'allocation du nouveau bloc ou du grand bloc.

74
SHH

Réponse courte:

Ce n'est pas le cas, il se trouve que c'est zéro dans votre cas.
(De plus, votre scénario de test ne montre pas que les données sont nulles. Il ne montre que si un élément est nul.)


Réponse longue:

Lorsque vous appelez malloc(), deux choses se produisent:

  1. Il recycle la mémoire précédemment allouée et libérée du même processus.
  2. Il demande de nouvelles pages au système d'exploitation.

Dans le premier cas, la mémoire contiendra les données restantes des allocations précédentes. Ce ne sera donc pas zéro. C'est le cas habituel lors de l'exécution de petites allocations.

Dans le second cas, la mémoire proviendra du système d'exploitation. Cela se produit lorsque le programme manque de mémoire - ou lorsque vous demandez une allocation très importante. (comme c'est le cas dans votre exemple)

Voici le problème: La mémoire provenant du système d'exploitation sera mise à zéro pour sécurité raisons. *

Lorsque le système d'exploitation vous donne de la mémoire, il aurait pu être libéré d'un processus différent. Pour que cette mémoire puisse contenir des informations sensibles telles qu'un mot de passe. Donc, pour vous empêcher de lire ces données, le système d'exploitation les mettra à zéro avant de vous les fournir.

* Je note que la norme C ne dit rien à ce sujet. Il s'agit strictement d'un comportement du système d'exploitation. Cette remise à zéro peut donc ou non être présente sur les systèmes où la sécurité n'est pas un problème.


Pour donner plus d'informations sur les performances:

Comme @R. mentionne dans les commentaires, cette mise à zéro est la raison pour laquelle vous devez toujours tilisez calloc() au lieu de malloc() + memset() . calloc() peut profiter de ce fait pour éviter une memset() distincte.


D'un autre côté, cette réduction à zéro est parfois un goulot d'étranglement des performances. Dans certaines applications numériques (comme la FFT déplacée ), vous devez allouer un énorme morceau de mémoire de travail. Utilisez-le pour exécuter n'importe quel algorithme, puis libérez-le.

Dans ces cas, la mise à zéro est inutile et équivaut à de la surcharge pure.

L'exemple le plus extrême que j'ai vu est une surcharge de mise à zéro de 20 secondes pour une opération de 70 secondes avec un tampon de travail de 48 Go. (Environ 30% de frais généraux.) (Accordé: la machine manquait de bande passante mémoire.)

La solution évidente est de simplement réutiliser la mémoire manuellement. Mais cela nécessite souvent de percer les interfaces établies. (surtout si cela fait partie d'une routine de bibliothèque)

168
Mysticial

Le système d'exploitation effacera généralement les nouvelles pages de mémoire qu'il envoie à votre processus afin qu'il ne puisse pas regarder les données d'un processus plus ancien. Cela signifie que la première fois que vous initialisez une variable (ou quelque chose de malloc), elle sera souvent nulle mais si vous réutilisez jamais cette mémoire (en la libérant et en recommençant, par exemple), tous les paris sont désactivés.

Cette incohérence est précisément la raison pour laquelle les variables non initialisées sont si difficiles à trouver.


En ce qui concerne les frais généraux de performances indésirables, éviter un comportement non spécifié est probablement plus important. Quelle que soit la petite amélioration des performances que vous pourriez gagner dans ce cas, cela ne compensera pas les bogues difficiles à trouver auxquels vous devrez faire face si quelqu'un modifie légèrement les codes (brisant les hypothèses précédentes) ou le transfère vers un autre système (où les hypothèses peuvent avoir été invalides en premier lieu).

21
hugomg

Pourquoi supposez-vous que malloc() s'initialise à zéro? Il se trouve que le premier appel à malloc() entraîne un appel aux appels système sbrk ou mmap, qui allouent une page de mémoire à partir du système d'exploitation. L'OS est obligé de fournir une mémoire initialisée à zéro pour des raisons de sécurité (sinon, les données d'autres processus deviennent visibles!). Donc, vous pourriez penser là-bas - le système d'exploitation perd du temps à mettre à zéro la page. Mais non! Sous Linux, il existe une page singleton spéciale à l'échelle du système appelée "page zéro" et cette page sera mappée en tant que copie sur écriture, ce qui signifie que seulement lorsque vous écrivez réellement sur cette page, le système d'exploitation allouera une autre page et l'initialiser. J'espère donc que cela répond à votre question concernant les performances. Le modèle de pagination de la mémoire permet une utilisation paresseuse de la mémoire en prenant en charge la capacité de mappage multiple de la même page ainsi que la possibilité de gérer le cas lors de la première écriture.

Si vous appelez free(), l'allocateur glibc renverra la région à ses listes libres, et lorsque malloc() sera appelé à nouveau, vous pourriez obtenir cette même région, mais sale avec les données précédentes. Finalement, free() peut retourner la mémoire au système d'exploitation en appelant à nouveau les appels système.

Notez que la glibcpage de manuel sur malloc() dit strictement que la mémoire n'est pas effacée, donc par le "contrat" ​​sur l'API, vous ne pouvez pas supposer qu'elle ne soit effacé. Voici l'extrait original:

malloc () alloue des octets de taille et renvoie un pointeur vers la mémoire allouée.
La mémoire n'est pas effacée. Si la taille est 0, malloc () retourne soit NULL, soit une valeur de pointeur unique qui peut ensuite être transmise avec succès à free ().

Si vous le souhaitez, vous pouvez en savoir plus sur cette documentation si vous êtes préoccupé par les performances ou d'autres effets secondaires.

17
Dan Aloni

J'ai modifié votre exemple pour contenir 2 allocations identiques. Maintenant, il est facile de voir que malloc ne met pas à zéro la mémoire d'initialisation.

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

int main(void)
{
    {
      double *a = malloc(sizeof(double)*100);
      *a = 100;
      printf("%f\n", *a);
      free(a);
    }
    {
      double *a = malloc(sizeof(double)*100);
      printf("%f\n", *a);
      free(a);
    }

    return 0;
}

Sortie avec gcc 4.3.4

100.000000
100.000000
14
Praetorian

De gnu.org :

De très gros blocs (beaucoup plus gros qu'une page) sont alloués avec mmap (anonyme ou via/dev/zero ) par cette implémentation.

3
TomaszK

La norme ne dicte pas que malloc() doit initialiser les valeurs à zéro. Il se produit simplement sur votre plate-forme qu'elle peut être définie sur zéro, ou elle peut être nulle au moment précis où vous lisez cette valeur.

2
user418748

Votre code ne montre pas que malloc initialise sa mémoire à 0. Cela pourrait être fait par le système d'exploitation, avant le démarrage du programme. Pour voir ce qui est le cas, écrivez une valeur différente dans la mémoire, libérez-la et appelez à nouveau malloc. Vous obtiendrez probablement la même adresse, mais vous devrez vérifier cela. Si c'est le cas, vous pouvez voir ce qu'il contient. Laissez nous savoir!

2
TonyK

Savez-vous qu'il est définitivement en cours d'initialisation? Est-il possible que la zone retournée par malloc () ait souvent 0 au début?

0
user23743

Jamais jamais compter sur n'importe lequel compilateur pour générer du code qui initialisera la mémoire à n'importe quoi. malloc renvoie simplement un pointeur sur n octets de mémoire quelque part diable, il pourrait même être en swap.

Si le contenu de la mémoire est critique, initialisez-le vous-même.

0
FlyingGuy