Okay, ich denke, wir sind uns alle einig, dass das, was mit dem folgenden Code passiert, undefiniert ist, je nachdem, was übergeben wird.
void deleteForMe(int* pointer)
{
delete[] pointer;
}
Der Zeiger kann viele verschiedene Dinge sein, und daher delete[]
ist es undefiniert , eine bedingungslose Ausführung durchzuführen . Nehmen wir jedoch an, dass wir tatsächlich einen Array-Zeiger übergeben.
int main()
{
int* arr = new int[5];
deleteForMe(arr);
return 0;
}
Meine Frage ist, in diesem Fall , in dem der Zeiger ist ein Array, wer ist es, das weiß? Ich meine, aus Sicht der Sprache / des Compilers hat es keine Ahnung, ob es sich arr
um einen Array-Zeiger oder einen Zeiger auf ein einzelnes Int handelt. Heck, es weiß nicht einmal, ob arr
dynamisch erstellt wurde. Wenn ich stattdessen Folgendes tue,
int main()
{
int* num = new int(1);
deleteForMe(num);
return 0;
}
Das Betriebssystem ist intelligent genug, um nur einen Int zu löschen und keine Art von "Amoklauf" durchzuführen, indem der Rest des Speichers über diesen Punkt hinaus gelöscht wird (im Gegensatz zu strlen
und einer nicht \0
terminierten Zeichenfolge - es wird so lange fortgesetzt, bis es passiert Treffer 0).
Wessen Aufgabe ist es also, sich an diese Dinge zu erinnern? Hält das Betriebssystem eine Art Aufzeichnung im Hintergrund? (Ich meine, mir ist klar, dass ich diesen Beitrag damit begonnen habe, dass das, was passiert, undefiniert ist, aber Tatsache ist, dass das Szenario „Amoklauf“ nicht vorkommt, also erinnert sich in der praktischen Welt jemand daran.)
Antworten:
Der Compiler weiß nicht, dass es sich um ein Array handelt, er vertraut dem Programmierer. Das Löschen eines Zeigers auf eine einzelne
int
mitdelete []
würde zu undefiniertem Verhalten führen. Ihr zweitesmain()
Beispiel ist unsicher, auch wenn es nicht sofort abstürzt.Der Compiler muss nachverfolgen, wie viele Objekte irgendwie gelöscht werden müssen. Dies kann geschehen, indem genug zu viel zugewiesen wird, um die Arraygröße zu speichern. Weitere Informationen finden Sie in den C ++ Super-FAQ .
quelle
Eine Frage, die die bisher gegebenen Antworten nicht zu beantworten scheinen: Wenn die Laufzeitbibliotheken (nicht wirklich das Betriebssystem) die Anzahl der Dinge im Array verfolgen können, warum brauchen wir dann überhaupt die
delete[]
Syntax? Warum kann nicht ein einzigesdelete
Formular verwendet werden, um alle Löschvorgänge zu verarbeiten?Die Antwort darauf geht auf die Wurzeln von C ++ als C-kompatible Sprache zurück (nach der es nicht mehr wirklich strebt). Stroustrups Philosophie war, dass der Programmierer nicht für Funktionen bezahlen muss, die er nicht verwendet. Wenn sie keine Arrays verwenden, sollten sie nicht die Kosten für Objekt-Arrays für jeden zugewiesenen Speicherblock tragen müssen.
Das heißt, wenn Ihr Code dies einfach tut
Dann sollte der zugewiesene Speicherplatz
foo
keinen zusätzlichen Overhead enthalten, der zur Unterstützung von Arrays von erforderlich wäreFoo
.Da nur Array-Zuordnungen so eingerichtet sind, dass sie die zusätzlichen Informationen zur Array-Größe enthalten, müssen Sie die Laufzeitbibliotheken anweisen, beim Löschen der Objekte nach diesen Informationen zu suchen. Deshalb müssen wir verwenden
statt nur
Wenn bar ein Zeiger auf ein Array ist.
Für die meisten von uns (ich selbst eingeschlossen) scheint diese Aufregung um ein paar zusätzliche Bytes Speicher heutzutage kurios zu sein. Es gibt jedoch immer noch Situationen, in denen das Speichern einiger Bytes (von einer möglicherweise sehr hohen Anzahl von Speicherblöcken) wichtig sein kann.
quelle
Ja, das Betriebssystem hält einige Dinge im Hintergrund. Zum Beispiel, wenn Sie ausführen
Das Betriebssystem kann 4 zusätzliche Bytes zuweisen, die Größe der Zuweisung in den ersten 4 Bytes des zugewiesenen Speichers speichern und einen Versatzzeiger zurückgeben (dh es weist Speicherplätze 1000 bis 1024 zu, aber der Zeiger gibt Punkte 1004 mit Positionen 1000 zurück. 1003 Speichern der Größe der Zuordnung). Wenn delete aufgerufen wird, kann es 4 Bytes anzeigen, bevor der Zeiger an ihn übergeben wird, um die Größe der Zuordnung zu ermitteln.
Ich bin sicher, dass es andere Möglichkeiten gibt, die Größe einer Zuordnung zu verfolgen, aber das ist eine Option.
quelle
for(int i = 0; i < *(arrayPointer - 1); i++){ }
Dies ist dieser Frage sehr ähnlich und enthält viele der Details, nach denen Sie suchen.
Es reicht jedoch zu sagen, dass es nicht die Aufgabe des Betriebssystems ist, dies zu verfolgen. Es sind tatsächlich die Laufzeitbibliotheken oder der zugrunde liegende Speichermanager, die die Größe des Arrays verfolgen. Dies erfolgt normalerweise durch Zuweisen von zusätzlichem Speicher im Voraus und Speichern der Größe des Arrays an diesem Speicherort (die meisten verwenden einen Kopfknoten).
Dies kann bei einigen Implementierungen angezeigt werden, indem der folgende Code ausgeführt wird
quelle
size_t size = *(reinterpret_cast<size_t *>(pArray) - 1)
stattdessendelete
oderdelete[]
würde wahrscheinlich beide den zugewiesenen Speicher freigeben (Speicher zeigt), aber der große Unterschied besteht darin, dassdelete
auf einem Array nicht der Destruktor jedes Elements des Arrays aufgerufen wird.Wie auch immer, mischen
new/new[]
unddelete/delete[]
ist wahrscheinlich UB.quelle
Es weiß nicht, dass es sich um ein Array handelt, deshalb müssen Sie
delete[]
anstelle von normalem Alt lieferndelete
.quelle
Ich hatte eine ähnliche Frage dazu. In C weisen Sie Speicher mit malloc () (oder einer ähnlichen Funktion) zu und löschen ihn mit free (). Es gibt nur ein malloc (), das einfach eine bestimmte Anzahl von Bytes zuweist. Es gibt nur eine freie (), die einfach einen Zeiger als Parameter verwendet.
Warum können Sie in C den Zeiger einfach an free übergeben, aber in C ++ müssen Sie angeben, ob es sich um ein Array oder eine einzelne Variable handelt?
Ich habe gelernt, dass die Antwort mit Klassendestruktoren zu tun hat.
Wenn Sie eine Instanz einer Klasse MyClass zuweisen ...
Wenn Sie es mit delete löschen, erhalten Sie möglicherweise nur den Destruktor für die erste aufgerufene MyClass-Instanz. Wenn Sie delete [] verwenden, können Sie sicher sein, dass der Destruktor für alle Instanzen im Array aufgerufen wird.
DAS ist der wichtige Unterschied. Wenn Sie einfach mit Standardtypen (z. B. int) arbeiten, wird dieses Problem nicht wirklich auftreten. Außerdem sollten Sie sich daran erinnern, dass das Verhalten für die Verwendung von delete bei new [] und delete [] bei new undefiniert ist - es funktioniert möglicherweise nicht auf jedem Compiler / System auf die gleiche Weise.
quelle
Es liegt an der Laufzeit, die für die Speicherzuweisung verantwortlich ist, genauso wie Sie ein mit malloc in Standard C erstelltes Array mit free löschen können. Ich denke, jeder Compiler implementiert es anders. Eine übliche Methode besteht darin, eine zusätzliche Zelle für die Arraygröße zuzuweisen.
Die Laufzeit ist jedoch nicht intelligent genug, um zu erkennen, ob es sich um ein Array oder einen Zeiger handelt. Sie müssen dies mitteilen. Wenn Sie sich irren, löschen Sie entweder nicht richtig (z. B. ptr anstelle von Array) oder Am Ende nehmen Sie einen nicht verwandten Wert für die Größe und verursachen erheblichen Schaden.
quelle
Einer der Ansätze für Compiler besteht darin, etwas mehr Speicher zuzuweisen und die Anzahl der Elemente im head-Element zu speichern.
Beispiel, wie es gemacht werden könnte: Hier
Der Compiler weist eine Größe von (int) * 5 Bytes zu.
Wird
4
in erstensizeof(int)
Bytes gespeichertund setzen
i
Zeigt also
i
auf ein Array von 4 Elementen, nicht auf 5.Und
wird folgendermaßen verarbeitet
quelle
Semantisch können beide Versionen des Löschoperators in C ++ jeden Zeiger "essen"; Wenn jedoch ein Zeiger auf ein einzelnes Objekt gegeben wird,
delete[]
führt dies zu UB, was bedeutet, dass alles passieren kann, einschließlich eines Systemabsturzes oder gar nichts.In C ++ muss der Programmierer die richtige Version des Löschoperators auswählen, abhängig vom Thema der Freigabe: Array oder einzelnes Objekt.
Wenn der Compiler automatisch feststellen könnte, ob ein an den Löschoperator übergebener Zeiger ein Zeigerarray ist, gibt es in C ++ nur einen Löschoperator, was in beiden Fällen ausreichen würde.
quelle
Stimmen Sie zu, dass der Compiler nicht weiß, ob es sich um ein Array handelt oder nicht. Es liegt am Programmierer.
Der Compiler verfolgt manchmal, wie viele Objekte gelöscht werden müssen, indem er genug zu viel zuweist, um die Arraygröße zu speichern, dies ist jedoch nicht immer erforderlich.
Eine vollständige Spezifikation für die Zuweisung von zusätzlichem Speicher finden Sie in C ++ ABI (wie Compiler implementiert werden): Itanium C ++ ABI: Neue Cookies für den Array-Operator
quelle
Sie können delete nicht für ein Array und delete [] nicht für ein Nicht-Array verwenden.
quelle
"undefiniertes Verhalten" bedeutet einfach, dass die Sprachspezifikation keine Garantie dafür gibt, was passieren wird. Es bedeutet nicht unbedingt, dass etwas Schlimmes passieren wird.
Hier gibt es normalerweise zwei Schichten. Der zugrunde liegende Speichermanager und die C ++ - Implementierung.
Im Allgemeinen merkt sich der Speichermanager (unter anderem) die Größe des zugewiesenen Speicherblocks. Dies kann größer sein als der Block, den die C ++ - Implementierung angefordert hat. Normalerweise speichert der Speichermanager seine Metadaten vor dem zugewiesenen Speicherblock.
Die C ++ - Implementierung merkt sich im Allgemeinen nur dann die Größe des Arrays, wenn dies für eigene Zwecke erforderlich ist, normalerweise, weil der Typ einen nicht-trivalenten Destruktor hat.
Für Typen mit einem trivialen Destruktor ist die Implementierung von "delete" und "delete []" normalerweise dieselbe. Die C ++ - Implementierung übergibt den Zeiger einfach an den zugrunde liegenden Speichermanager. Etwas wie
Andererseits sind bei Typen mit einem nicht trivialen Destruktor "delete" und "delete []" wahrscheinlich unterschiedlich. "Löschen" wäre so etwas wie (wobei T der Typ ist, auf den der Zeiger zeigt)
Während "delete []" so etwas wie wäre.
quelle
Durchlaufen Sie ein Array von Objekten und rufen Sie den Destruktor für jedes Objekt auf. Ich habe diesen einfachen Code erstellt, der neue [] und delete [] Ausdrücke überlädt und eine Vorlagenfunktion bereitstellt, mit der Speicher freigegeben und bei Bedarf für jedes Objekt Destruktor aufgerufen werden kann:
quelle
Definieren Sie einfach einen Destruktor innerhalb einer Klasse und führen Sie Ihren Code mit beiden Syntaxen aus
Je nach Ausgabe können Sie die Lösungen finden
quelle
Die Antwort:
int * pArray = new int [5];
int size = * (pArray-1);
Der oben angegebene Wert ist nicht korrekt und führt zu einem ungültigen Wert. Die "-1" zählt Elemente Unter 64-Bit-Windows-Betriebssystemen befindet sich die richtige Puffergröße in der Ptr-4-Byte-Adresse
quelle