C ++ new int [0] - wird Speicher zugewiesen?

240

Eine einfache Test-App:

cout << new int[0] << endl;

Ausgänge:

0x876c0b8

Es sieht also so aus, als ob es funktioniert. Was sagt der Standard dazu? Ist es immer legal, einen leeren Speicherblock "zuzuweisen"?


quelle
4
+1 Sehr interessante Frage - obwohl ich nicht sicher bin, wie wichtig es in echtem Code ist.
Zifre
37
@Zifre: Ich bitte um Neugier, aber es könnte in der realen Welt von Bedeutung sein, z. B. wenn die Größe der zugewiesenen Speicherblöcke auf irgendeine Weise berechnet wird und das Ergebnis der Berechnung Null sein könnte, besteht keine direkte Notwendigkeit, Ausnahmen hinzuzufügen Blöcke mit der Größe Null nicht zuzuweisen. Weil sie fehlerfrei zugewiesen und gelöscht werden sollten (wenn nur der Block mit der Größe Null nicht dereferenziert wird). Im Allgemeinen ergibt dies eine breitere Abstraktion dessen, was ein Speicherblock ist.
2
@ emg-2: In deiner Beispielsituation wäre das eigentlich egal, da delete [] für einen NULL-Zeiger völlig legal ist :-).
Evan Teran
2
Es ist nur tangential verwandt - also kommentiere ich hier -, aber C ++ stellt in vielerlei Hinsicht sicher, dass bestimmte Objekte eindeutige Adressen haben ... auch wenn sie keinen expliziten Speicherplatz benötigen. Ein verwandtes Experiment wäre, die Größe einer leeren Struktur zu überprüfen. Oder ein Array dieser Struktur.
Drew Dormann
2
Um den Kommentar von Shmoopty näher zu erläutern: Insbesondere beim Programmieren mit Vorlagen (z. B. Richtlinienklassenvorlagen wie std :: allocator) ist es in C ++ üblich, Objekte mit der Größe Null zu haben. Generischer Code muss solche Objekte möglicherweise dynamisch zuweisen und Zeiger darauf verwenden, um die Objektidentität zu vergleichen. Aus diesem Grund gibt operator new () eindeutige Zeiger für Anforderungen mit der Größe Null zurück. Obwohl dies weniger wichtig / üblich ist, gilt die gleiche Argumentation für die Array-Zuweisung und den Operator new [] ().
Trevor Robinson

Antworten:

233

Ab 5.3.4 / 7

Wenn der Wert des Ausdrucks in einem Direct-New-Deklarator Null ist, wird die Zuordnungsfunktion aufgerufen, um ein Array ohne Elemente zuzuweisen.

Ab 3.7.3.1/2

Der Effekt der Dereferenzierung eines Zeigers, der als Anforderung für die Größe Null zurückgegeben wird, ist undefiniert.

Ebenfalls

Selbst wenn die Größe des angeforderten Speicherplatzes [von new] Null ist, kann die Anforderung fehlschlagen.

Das bedeutet, dass Sie dies tun können, aber Sie können den Speicher, den Sie erhalten, nicht legal (auf alle Plattformen genau definiert) dereferenzieren - Sie können ihn nur an Array Delete übergeben - und Sie sollten ihn löschen.

Hier ist eine interessante Fußnote (dh kein normativer Teil des Standards, aber zu Expository-Zwecken enthalten), die dem Satz vom 3.7.3.1/2 beigefügt ist

[32. Der Operator new () soll durch Aufrufen von malloc () oder calloc () implementiert werden können, sodass die Regeln im Wesentlichen dieselben sind. C ++ unterscheidet sich von C darin, dass eine Nullanforderung erforderlich ist, um einen Nicht-Nullzeiger zurückzugeben.]

Faisal Vali
quelle
Ich bekomme einen Speicherverlust, wenn ich nicht lösche. Wird es erwartet? Zumindest habe ich nicht damit gerechnet.
EralpB
12
@EralpB: Im Gegenteil, selbst wenn Ihre Anfrage Null ist, erfolgt diese Zuordnung auf dem Heap, wo eine Anfrage mehrere Buchhaltungsvorgänge impliziert, wie das Zuweisen und Initialisieren von Heap-Wachen vor und nach der vom Allokator angegebenen Zone, das Einfügen in Freelisten oder andere komplexe schreckliche Strukturen. Um es zu befreien, muss man die Buchhaltung rückwärts machen.
v.oddou
3
@EralpB Ja, ich denke, Sie können jedes Mal einen Speicherverlust erwarten, wenn Sie a nicht new[]mit a ausgleichen delete[]- unabhängig von der Größe. Insbesondere wenn Sie anrufen new[i], benötigen Sie etwas mehr Speicher als den, den Sie zuweisen möchten, um die Größe des Arrays zu speichern (das später delete[]bei der Freigabe verwendet wird)
pqnet
24

Ja, es ist legal, ein Array mit der Größe Null wie dieses zuzuweisen. Sie müssen es aber auch löschen.


quelle
1
Hast du ein Zitat dafür? Wir alle wissen, dass int ar[0];das illegal ist, warum ist neues OK?
Motti
4
Es ist interessant, dass C ++ nicht so stark darin ist, Objekte mit der Größe Null zu verbieten. Denken Sie an die Optimierung der leeren Basisklasse: Auch hier kann ein leeres Unterobjekt der Basisklasse die Größe Null haben. Im Gegensatz dazu unternimmt der C-Standard große Anstrengungen, um sicherzustellen, dass niemals Objekte mit der Größe Null erstellt werden: Bei der Definition von malloc (0) heißt es, dass malloc beispielsweise mit einem Argument ungleich Null ausgeführt wird. Und in struct {...; T n []; }; Wenn dem Array (FAM) kein Speicherplatz zugewiesen ist, verhält es sich so, als hätte "n" ein Element. (In beiden Fällen ist die Verwendung des Objekts in irgendeiner Weise UB, wie xn [0])
Johannes Schaub - litb
1
Ich denke, es sizeof (type)wird erwartet, dass es niemals Null zurückgibt. Siehe zum Beispiel: stackoverflow.com/questions/2632021/can-sizeof-return-0-zero
pqnet
Selbst die sogenannte "leere Basisklassenoptimierung" ist nur relevant, weil darauf bestanden wird, dass alle Objekte (im Gegensatz zu allen Bytes innerhalb von Objekten) eindeutige Adressen haben müssen. C ++ hätte einfacher gestaltet werden können, wenn Code erforderlich gewesen wäre, der sich tatsächlich um Objekte mit eindeutigen Adressen kümmerte, um sicherzustellen, dass sie eine Größe ungleich Null hatten.
Supercat
15

Was sagt der Standard dazu? Ist es immer legal, einen leeren Speicherblock "zuzuweisen"?

Jedes Objekt hat eine eindeutige Identität, dh eine eindeutige Adresse, die eine Länge ungleich Null impliziert (die tatsächliche Speichermenge wird stillschweigend erhöht, wenn Sie nach null Byte fragen).

Wenn Sie mehr als eines dieser Objekte zugewiesen haben, werden sie unterschiedliche Adressen haben.

ChrisW
quelle
"Jedes Objekt hat eine eindeutige Identität, dh eine eindeutige Adresse, die eine Länge ungleich Null impliziert" - wahr, aber falsch. Das Objekt hat eine Adresse, aber der Zeiger auf das Objekt kann auf einen zufälligen Speicher zeigen. "Die tatsächliche Speichermenge wird stillschweigend erhöht, wenn Sie nach null Bytes fragen" - nicht sicher. operator []speichert auch die Größe des Arrays irgendwo (siehe isocpp.org/wiki/faq/freestore-mgmt#num-elems-in-new-array ). Wenn die Implementierung den Daten also die Byteanzahl zuweist, kann sie nur die Byteanzahl und 0 Bytes für die Daten zuweisen und einen 1-nach-letzten-Zeiger zurückgeben.
Steed
Eine Länge des zugewiesenen Speichers ungleich Null, keine Länge des verwendbaren Speichers ungleich Null. Mein Punkt war, dass zwei Zeiger auf zwei verschiedene Objekte nicht auf dieselbe Adresse zeigen sollten.
ChrisW
1
Der Standard ( open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf ) sagt tatsächlich (3.7.4.1/2), dass unterschiedliche Aufrufe operator new[]unterschiedliche Zeiger zurückgeben sollen. BTW, das new expressionhat einige zusätzliche Regeln (5.3.4). Ich konnte keine Ahnung finden , die newmit 0 Größe tatsächlich benötigt etwas zu vergeben. Entschuldigung, ich habe abgelehnt, weil ich finde, dass Ihre Antwort nicht die Fragen beantwortet, sondern einige kontroverse Aussagen enthält.
Steed
@Steed Der Speicher zum Speichern der Länge des Blocks kann implizit unter Verwendung des Adressraums berechnet werden. Für Arrays mit der Größe Null könnte es beispielsweise möglich sein, dass die new[]Implementierung Adressen in einem Bereich
zurückgibt, in dem
@pqnet: Eine Implementierung von guter Qualität sollte eine unbegrenzte Anzahl von Neu- / Löschzyklen ermöglichen, nicht wahr? Auf einer Plattform mit 64-Bit-Zeigern kann es sinnvoll sein zu sagen, dass ein Computer, der eine while(!exitRequested) { char *p = new char[0]; delete [] p; }Schleife ohne Recycling von Zeigern ausführt, in Staub zerfällt, bevor ihm möglicherweise der Adressraum ausgeht. Auf einer Plattform mit 32-Bit-Zeigern wäre dies jedoch ein weit weniger vernünftige Annahme.
Supercat
14

Ja, es ist völlig legal, einen 0Block mit Größe zuzuweisen new. Sie können damit einfach nichts Nützliches anfangen, da Sie keine gültigen Daten haben, auf die Sie zugreifen können. int[0] = 5;ist illegal.

Ich glaube jedoch, dass der Standard Dinge wie malloc(0)die Rückkehr zulässt NULL.

Sie benötigen weiterhin den delete []Zeiger, den Sie von der Zuordnung erhalten.

Evan Teran
quelle
5
In Bezug auf Malloc haben Sie Recht - es ist die Implementierung definiert. Dies wird oft als Fehlfunktion angesehen.
Ich nehme an, eine interessante Frage ist: Kann die nothrow-Version von new NULL zurückgeben, wenn eine Größe von 0 angegeben wird?
Evan Teran
1
Die Semantik von new und malloc ist zumindest durch den Standard in keiner Weise miteinander verbunden.
nicht zu sagen, dass sie ... speziell nach der Nothrow-Version von New gefragt haben.
Evan Teran
@Evan - Die Nothrow-Version von new gibt nur dann null zurück, wenn die Anforderung fehlschlägt - nicht nur, wenn die Anforderungsgröße 0 ist. @Neil - nicht normativ verknüpft - sondern absichtlich verknüpft (dh der Operator new kann in Bezug auf malloc implementiert werden, nicht jedoch der andere Weg herum) - siehe die Fußnote, die ich in meine Antwort aufgenommen habe
Faisal Vali
1

Seltsamerweise erfordert C ++, dass der Operator new einen legitimen Zeiger zurückgibt, selbst wenn null Bytes angefordert werden. (Das Erfordernis dieses seltsam klingenden Verhaltens vereinfacht die Dinge an anderer Stelle in der Sprache.)

Ich fand, dass Effective C ++ Third Edition in "Punkt 51: Konvention beim Schreiben von Neuem und Löschen einhalten".

Shuaihanhungry
quelle
0

Ich garantiere Ihnen, dass new int [0] Sie zusätzlichen Speicherplatz kostet, da ich es getestet habe.

Zum Beispiel die Speichernutzung von

int **arr = new int*[1000000000];

ist deutlich kleiner als

int **arr = new int*[1000000000];
for(int i =0; i < 1000000000; i++) {
    arr[i]=new int[0];
}

Die Speichernutzung des zweiten Code-Snippets minus der des ersten Code-Snippets ist der Speicher, der für die zahlreichen neuen int [0] verwendet wird.

Shi Jieming
quelle