Operator new initialisiert den Speicher auf Null

78

Es gibt einen solchen Code:

#include <iostream>

int main(){
  unsigned int* wsk2 = new unsigned int(5);
  std::cout << "wsk2: " << wsk2 << " " << *wsk2 << std::endl;
  delete wsk2;
  wsk2 = new unsigned int;
  std::cout << "wsk2: " << wsk2 << " " << *wsk2 << std::endl;
  return 0;
}

Ergebnis:

wsk2: 0x928e008 5
wsk2: 0x928e008 0

Ich habe gelesen, dass newder Speicher nicht mit Nullen initialisiert wird. Aber hier scheint es so. Wie funktioniert es?

scdmb
quelle
2
Wenn dies nicht der Fall ist, müssten Sie new unsigned int () verwenden. Das Lesen nicht initialisierter Variablen ist UB, eine 0 ist sicherlich möglich.
Hans Passant
1
Abgesehen davon ist in der Praxis ohnehin viel Speicher Null. Wenn Sie ein einfaches Malloc eines großen Speicherbereichs erstellen, ist auf einigen Systemen häufig alles Null, auf anderen meistens Null.
Nicholas Wilson
2
@NicholasWilson: Bei den meisten Systemen gilt dies nur, wenn der Speicher direkt vom Betriebssystem stammt (aus Sicherheitsgründen). Wenn es aus dem vom Allokator zwischengespeicherten Speicher stammt, enthält es höchstwahrscheinlich alles, was es getan hat, bevor es frei war () d. Dies ist auch ein Grund, warum solche Fehler manchmal nicht in Testfällen / Unit-Tests auftreten, sondern erst nach einer Weile, in der das "echte" Programm ausgeführt wird. Valgrind zur Rettung hier.
PlasmaHH
Ja. Das ist irgendwie mein Punkt. Wenn Sie eine neue Variable erstellen und feststellen, dass sie Null ist, können Sie nicht sofort davon ausgehen, dass etwas in Ihrem Programm sie auf Null gesetzt hat. Da der größte Teil des Speichers auf Null gesetzt ist, ist er wahrscheinlich noch nicht initialisiert. Ich denke, meine Bemerkung ist tatsächlich näher an der Beantwortung der Frage als die meisten der folgenden Antworten, weil der Fragesteller wissen wollte, warum nicht initialisiertes Gedächtnis Null sein könnte.
Nicholas Wilson
Versuchen Sie, ein Array zuzuweisen, alle Elemente auf einen Wert ungleich Null zu setzen, löschen Sie es dann [] und weisen Sie es erneut zu - nur das erste Element wird auf Null gesetzt - andere behalten ihre Werte bei - was meiner Meinung nach eine Besonderheit des Speicherzuweisers ist (unter Linux getestet) ).
Kyku

Antworten:

199

Es gibt zwei Versionen:

wsk = new unsigned int;      // default initialized (ie nothing happens)
wsk = new unsigned int();    // zero    initialized (ie set to 0)

Funktioniert auch für Arrays:

wsa = new unsigned int[5];   // default initialized (ie nothing happens)
wsa = new unsigned int[5](); // zero    initialized (ie all elements set to 0)

Als Antwort auf den Kommentar unten.

Ähm ... bist du sicher, dass new unsigned int[5]()die ganzen Zahlen Nullen sind?

Anscheinend schon:

[C ++ 11: 5.3.4 / 15]: Ein neuer Ausdruck, der ein Objekt vom Typ T erstellt, initialisiert dieses Objekt wie folgt: Wenn der neue Initialisierer weggelassen wird, wird das Objekt standardmäßig initialisiert (8.5). Wenn keine Initialisierung durchgeführt wird, hat das Objekt einen unbestimmten Wert. Andernfalls wird der Neuinitialisierer gemäß den Initialisierungsregeln von 8.5 für die Direktinitialisierung interpretiert.

#include <new>
#include <iostream>


int main()
{
    unsigned int   wsa[5] = {1,2,3,4,5};

    // Use placement new (to use a know piece of memory).
    // In the way described above.
    // 
    unsigned int*    wsp = new (wsa) unsigned int[5]();

    std::cout << wsa[0] << "\n";   // If these are zero then it worked as described.
    std::cout << wsa[1] << "\n";   // If they contain the numbers 1 - 5 then it failed.
    std::cout << wsa[2] << "\n";
    std::cout << wsa[3] << "\n";
    std::cout << wsa[4] << "\n";
}

Ergebnisse:

> g++ --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn)
Target: x86_64-apple-darwin13.2.0
Thread model: posix
> g++ t.cpp
> ./a.out
0
0
0
0
0
>
Martin York
quelle
Und was ist mit überlasteten new/new[]Betreibern? Wird new unsigned int[5]()das Array auch dann noch auf Null initialisiert, wenn es eine proprietäre, völlig unabhängige Implementierung hat?
SomeWittyUsername
2
@icepack: Dies operator newist eine vom Benutzer (Entwickler) definierte Funktion, die beliebig ausgeführt werden kann. Der Standard schränkt die Funktionsweise dieser Funktion nicht ein. Genauso wie er operator==alles tun kann, was der Benutzer definiert (er muss nicht einmal bool zurückgeben, geschweige denn auf Gleichheit testen (obwohl es eine schlechte Form ist, wenn dies nicht der Fall ist)).
Martin York
1
Vielen Dank. Um ganz klar zu sein: Die Implementierung meines proprietären Allokators hat nichts mit Null-Initialisierung zu tun. Bei Verwendung der ()Syntax wird der Puffer ohne Beziehung zum Typ des zugewiesenen Objekts und seinen Konstruktoren mit Null initialisiert . (Und ich nehme an, C ++ 11 ändert nichts an diesem Aspekt)
SomeWittyUsername
1
@ J3STER Das Übergeben eines Zeigers mit dem neuen Aufruf wird als Platzierung neu bezeichnet. Siehe 8.3.4 New. Warum wurde es geändert. Weil wir die Wertinitialisierung erzwingen. Siehe 11.6 Initializers. Beachten Sie, dass wir einen Zeiger auf den neuen Operator übergeben. Der Wert von wsawird NICHT geändert. Es wird die Adresse geändert, auf die verwiesen wird.
Martin York
21

operator newEs wird nicht garantiert, dass der Speicher für irgendetwas initialisiert wird, und der neue Ausdruck , der einen unsigned intohne einen neuen Initialisierer zuweist, hinterlässt dem Objekt einen unbestimmten Wert.

Das Lesen des Werts eines nicht initialisierten Objekts führt zu undefiniertem Verhalten . Undefiniertes Verhalten umfasst das Auswerten auf den Wert Null ohne negative Auswirkungen, kann jedoch dazu führen, dass etwas passiert. Sie sollten daher vermeiden, es zu verursachen.

In C ++ 11 wird als Sprache verwendet, dass die zugewiesenen Objekte standardmäßig initialisiert werden, was für Nichtklassentypen bedeutet, dass keine Initialisierung durchgeführt wird. Dies unterscheidet sich von der Bedeutung der Standardinitialisierung in C ++ 03.

CB Bailey
quelle
4

Bei einigen Compilern initialisiert die Debug-Version von new die Daten, aber es gibt sicherlich nichts, auf das Sie sich verlassen können.

Es ist auch möglich, dass der Speicher gerade 0 aus einer früheren Verwendung hatte. Gehen Sie nicht davon aus, dass zwischen Löschen und Neu nichts mit dem Speicher passiert ist. Im Hintergrund könnte etwas geschehen, das Sie nie bemerkt haben. Außerdem ist derselbe Zeigerwert möglicherweise nicht derselbe physische Speicher. Speicherseiten werden verschoben und ausgelagert. Ein Zeiger wird möglicherweise einer ganz anderen Position als zuvor zugeordnet.

Fazit: Wenn Sie einen Speicherort nicht speziell initialisiert haben, können Sie nichts über dessen Inhalt annehmen. Der Speichermanager weist möglicherweise erst dann einen bestimmten physischen Speicherort zu, wenn Sie den Speicher verwenden.

Die moderne Speicherverwaltung ist erstaunlich komplex, aber als C ++ - Programmierer ist Ihnen das eigentlich egal (meistens ‡). Spielen Sie nach den Regeln und Sie werden keine Probleme bekommen.

‡ Es könnte Sie interessieren, ob Sie optimieren, um Seitenfehler zu reduzieren.

Michael J.
quelle
3

Das ist nicht operator new, das ist der newBetreiber. Es gibt tatsächlich einen großen Unterschied! Der Unterschied besteht darin, dass operator newes sich um eine Funktion handelt, die Rohspeicher zurückgibt. Wenn Sie den newOperator verwenden, wird ein Konstruktor für Sie aufgerufen. Es ist der Konstruktor, der den Wert davon festlegt int, nicht operator new.

Ernest Friedman-Hill
quelle
5
Ich denke, der richtige Begriff ist "neuer Ausdruck", der den entsprechenden neuen Operator aufruft.
Kerrek SB