Warum initialisiert malloc die Werte in gcc auf 0?


77

Vielleicht ist es von Plattform zu Plattform unterschiedlich, aber

Wenn ich mit gcc kompiliere und den folgenden Code ausführe, erhalte ich in meinem Ubuntu 11.10 jedes Mal 0.

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

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

Warum verhält sich Malloc so, obwohl es Calloc gibt?

Bedeutet das nicht, dass es einen unerwünschten Leistungsaufwand gibt, nur um die Werte auf 0 zu initialisieren, auch wenn Sie dies manchmal nicht möchten?


EDIT: Oh, mein vorheriges Beispiel war nicht Initiazling, sondern verwendete zufällig "frischen" Block.

Was ich genau gesucht habe, war, warum es initialisiert wird, wenn es einen großen Block zuweist:

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)

Aber danke, dass Sie darauf hingewiesen haben, dass es beim Mallocing einen SICHERHEITSGRUND gibt! (Habe niemals drüber nachgedacht). Sicher muss es auf Null initialisiert werden, wenn ein neuer Block oder der große Block zugewiesen wird.


13
Haben Sie für einen realistischeren Test versucht, zuzuweisen, freizugeben und dann erneut zuzuweisen (möglicherweise mehrmals zu wiederholen)? Nur weil malloc beim ersten Mal einen mit Null initialisierten Speicher zurückgibt, heißt das nicht, dass Sie sich im Allgemeinen darauf verlassen können.
user786653

1
Es kann auch sein, dass der Speicher vom Betriebssystem oder so auf 0 gesetzt wurde und mallocnichts damit zu tun hat.
Seth Carnegie

Antworten:


177

Kurze Antwort:

Es ist nicht so, es ist einfach Null in Ihrem Fall.
(Auch Ihr Testfall zeigt nicht, dass die Daten Null sind. Er zeigt nur, wenn ein Element Null ist.)


Lange Antwort:

Wenn Sie anrufen malloc(), geschieht eines von zwei Dingen:

  1. Es recycelt Speicher, der zuvor zugewiesen und von demselben Prozess befreit wurde.
  2. Es fordert neue Seiten vom Betriebssystem an.

Im ersten Fall enthält der Speicher Datenreste aus früheren Zuordnungen. Es wird also nicht Null sein. Dies ist der übliche Fall bei kleinen Zuordnungen.

Im zweiten Fall stammt der Speicher vom Betriebssystem. Dies geschieht, wenn dem Programm der Speicher ausgeht - oder wenn Sie eine sehr große Zuordnung anfordern. (wie in Ihrem Beispiel)

Hier ist der Haken: Der vom Betriebssystem kommende Speicher wird aus Sicherheitsgründen auf Null gesetzt . *

Wenn das Betriebssystem Speicher bereitstellt, wurde es möglicherweise von einem anderen Prozess befreit. Dieser Speicher kann also vertrauliche Informationen wie ein Kennwort enthalten. Um zu verhindern, dass Sie solche Daten lesen, setzt das Betriebssystem sie auf Null, bevor es sie Ihnen gibt.

* Ich stelle fest, dass der C-Standard nichts darüber aussagt. Dies ist ausschließlich ein Betriebssystemverhalten. Diese Nullung kann also auf Systemen vorhanden sein, auf denen die Sicherheit keine Rolle spielt.


Um mehr Performance-Hintergrund zu geben:

Als @R. den Kommentaren erwähnt in dieser Nullung Deshalb sollten Sie immer verwenden calloc()anstelle von malloc()+memset() . calloc()kann diese Tatsache ausnutzen, um eine separate zu vermeiden memset().


Andererseits ist diese Nullung manchmal ein Leistungsengpass. In einigen numerischen Anwendungen (z. B. der fehlenden FFT ) müssen Sie einen großen Teil des Arbeitsspeichers zuweisen. Verwenden Sie es, um einen beliebigen Algorithmus auszuführen, und geben Sie ihn dann frei.

In diesen Fällen ist das Nullstellen nicht erforderlich und entspricht einem reinen Overhead.

Das extremste Beispiel, das ich gesehen habe, ist ein Null-Overhead von 20 Sekunden für einen 70-Sekunden-Vorgang mit einem 48-GB-Arbeitspuffer. (Ungefähr 30% Overhead.) (Zugegeben: Der Computer hatte einen Mangel an Speicherbandbreite.)

Die naheliegende Lösung besteht darin, den Speicher einfach manuell wiederzuverwenden. Dies erfordert jedoch häufig das Durchbrechen etablierter Schnittstellen. (besonders wenn es Teil einer Bibliotheksroutine ist)


14
Sie können jedoch immer noch nicht damit rechnen, dass es Null ist, es sei denn, Sie tun dies selbst (oder mit calloc, was dies für Sie erledigt, nachdem Sie Speicher vom Betriebssystem erhalten haben).
Greg Hewgill

Danke für deine Antwort. Ich hätte nie gedacht, dass es beim Mallocing ein Sicherheitsproblem geben würde!
SHH

34
Es ist subtil. Wenn das Betriebssystem Speicher bereitstellt, wurde es möglicherweise von einem anderen Prozess befreit. Dieser Speicher kann also vertrauliche Informationen wie ein Kennwort enthalten. Um zu verhindern, dass Sie solche Daten lesen, wird sie vom Betriebssystem auf Null gesetzt, bevor sie Ihnen zur Verfügung gestellt werden. Es handelt sich jedoch um ein Implementierungsdetail, das möglicherweise anders ist, beispielsweise bei einigen eingebetteten Systemen.
Mysticial

21
Dies ist ein wenig abseits von OPs Frage, aber eine Konsequenz dieses Effekts ist, dass Sie immer calloceher als malloc+ verwenden sollten, memsetwenn Sie null initialisierten Speicher möchten (zumindest für große Blöcke, bei denen die Zeit bis Null von Bedeutung sein könnte). malloc+ memsetverursacht immer hohe Kosten für das Schreiben in den gesamten Block, aber das System callockann die Tatsache ausnutzen, dass der neue anonyme Speicher zunächst mit Null gefüllt wird.
R .. GitHub STOP HELPING ICE

4
Die Antworten in dieser Frage können Ihnen helfen, das zu verstehen. Der Kernel kann mit Calloc betrügen, indem er nicht alle auf Null gesetzten Seiten ausschreibt, bis sie verwendet werden. Memset zwingt (anscheinend) das sofortige Ausschreiben der Seiten. Mehr Infos unter dem Link.
Thomasrutter

21

Das Betriebssystem löscht normalerweise neue Speicherseiten, die es an Ihren Prozess sendet, damit es die Daten eines älteren Prozesses nicht anzeigen kann. Dies bedeutet, dass beim ersten Initialisieren einer Variablen (oder eines Mallocs) diese häufig Null ist. Wenn Sie diesen Speicher jedoch jemals wiederverwenden (z. B. durch Freigeben und erneutes Mallocing), sind alle Wetten ungültig.

Diese Inkonsistenz ist genau der Grund, warum nicht initialisierte Variablen so schwer zu finden sind.


In Bezug auf den unerwünschten Leistungsaufwand ist es wahrscheinlich wichtiger, nicht angegebenes Verhalten zu vermeiden . Unabhängig davon, welchen kleinen Leistungsschub Sie in diesem Fall erzielen könnten, werden die schwer zu findenden Fehler, mit denen Sie sich befassen müssen, nicht kompensiert, wenn jemand die Codes geringfügig ändert (frühere Annahmen verletzt) ​​oder sie auf ein anderes System portiert (bei dem die Annahmen möglicherweise ungültig waren) an erster Stelle).


4
+1 ... nicht sicher, ob "wahrscheinlich" in dem fettgedruckten

18

Warum wird das malloc()auf Null initialisiert? Es ist einfach so, dass der erste Aufruf von malloc()zu einem Aufruf von sbrkoder führtmmapSystemaufrufe, die eine Seite des Speichers vom Betriebssystem zuweisen. Das Betriebssystem ist aus Sicherheitsgründen verpflichtet, nullinitialisierten Speicher bereitzustellen (andernfalls werden Daten aus anderen Prozessen sichtbar!). Sie könnten also denken, dass das Betriebssystem Zeit damit verschwendet, die Seite auf Null zu setzen. Aber nein! Unter Linux gibt es eine spezielle systemweite Singleton-Seite namens "Zero Page". Diese Seite wird als "Copy-On-Write" zugeordnet. Dies bedeutet, dass das Betriebssystem nur dann eine weitere Seite und zuweist, wenn Sie tatsächlich auf diese Seite schreiben initialisiere es. Ich hoffe, dies beantwortet Ihre Frage zur Leistung. Das Speicher-Paging-Modell ermöglicht eine verzögerte Speichernutzung, indem es die Möglichkeit der Mehrfachzuordnung derselben Seite sowie die Möglichkeit unterstützt, den Fall beim ersten Schreiben zu behandeln.

Wenn Sie anrufen free(), glibckehrt der Allokator die Region zu seinen freien Listen zurück. Wenn Sie malloc()erneut aufgerufen werden, erhalten Sie möglicherweise dieselbe Region, die jedoch mit den vorherigen Daten verschmutzt ist. Schließlich free()könnte die Erinnerung an das Betriebssystem zurückkehren , indem erneut Systemaufrufe aufruft.

Beachten Sie, dass der glibc Mann Seite auf malloc()streng sagt , dass der Speicher nicht gelöscht wird, so durch den „Vertrag“ auf der API können Sie nicht davon ausgehen , dass es gelöscht wird erhalten. Hier ist der Originalauszug:

malloc () weist Größenbytes zu und gibt einen Zeiger auf den zugewiesenen Speicher zurück.
Der Speicher wird nicht gelöscht. Wenn die Größe 0 ist, gibt malloc () entweder NULL oder einen eindeutigen Zeigerwert zurück, der später erfolgreich an free () übergeben werden kann.

Wenn Sie möchten, können Sie mehr über diese Dokumentation lesen, wenn Sie sich Sorgen über die Leistung oder andere Nebenwirkungen machen.


15

Ich habe Ihr Beispiel so geändert, dass es zwei identische Zuordnungen enthält. Jetzt ist es leicht zu erkennen, mallocdass der Speicher nicht auf Null gesetzt wird.

#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;
}

Ausgabe mit gcc 4.3.4

100.000000
100.000000

Ich habe versucht, was Sie getan haben, und wenn ich dann nur 100 Bytes zuweise, obwohl der Zeiger auf die gleiche Adresse zeigt, ist der Wert an dieser Adresse unterschiedlich. Wenn ich 400 Bytes oder mehr zuordne, sind sowohl der Zeigerwert als auch der Wert im Speicher gleich. Was denkst du könnte die Ursache sein?
yoyo_fun

3

Von gnu.org :

Sehr große Blöcke (viel größer als eine Seite) werden von dieser Implementierung mit mmap (anonym oder über / dev / zero ) zugewiesen .


Das OP ist jedoch in kleinen Schritten mallocing. Hat diese Referenz, die Sie gefunden haben, auch etwas damit zu tun?
Hugomg

2

Der Standard schreibt nicht vor, dass malloc()die Werte auf Null initialisiert werden sollen. Auf Ihrer Plattform kann es nur vorkommen, dass sie auf Null gesetzt wird oder zu dem Zeitpunkt, zu dem Sie diesen Wert gelesen haben, auf Null gesetzt wurde.


2

Ihr Code zeigt nicht an, dass mallocsein Speicher auf 0 initialisiert wird. Dies kann vom Betriebssystem durchgeführt werden, bevor das Programm gestartet wird. Um zu sehen, was der Fall ist, schreiben Sie einen anderen Wert in den Speicher, geben Sie ihn frei und rufen Sie malloc erneut auf. Sie werden wahrscheinlich die gleiche Adresse erhalten, müssen dies jedoch überprüfen. Wenn ja, können Sie sehen, was es enthält. Lass uns wissen!


0

Wissen Sie, dass es definitiv initialisiert wird? Ist es möglich, dass der von malloc () zurückgegebene Bereich am Anfang nur häufig 0 hat?


0

Verlassen Sie sich niemals darauf, dass ein Compiler Code generiert, der den Speicher für irgendetwas initialisiert. malloc gibt einfach einen Zeiger auf n Bytes Speicher an einem Ort zurück, an dem es sich möglicherweise sogar um einen Swap handelt.

Wenn der Inhalt des Speichers kritisch ist, initialisieren Sie ihn selbst.


4
Außer in Fällen, in denen die Sprache garantiert, dass sie initialisiert wird. Statische Objekte ohne explizite Initialisierung werden implizit auf Null initialisiert.
Keith Thompson
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.