Angenommen, ich habe den folgenden Code:
void* my_alloc (size_t size)
{
return new char [size];
}
void my_free (void* ptr)
{
delete [] ptr;
}
Ist das sicher? Oder muss vor dem Löschen ptr
gegossen werden char*
?
c++
memory-management
casting
void-pointers
An̲̳̳drew
quelle
quelle
Antworten:
Es kommt auf "sicher" an. Dies funktioniert normalerweise, da Informationen zusammen mit dem Zeiger auf die Zuordnung selbst gespeichert werden, sodass der Deallocator sie an die richtige Stelle zurückgeben kann. In diesem Sinne ist es "sicher", solange Ihr Allokator interne Grenz-Tags verwendet. (Viele tun es.)
Wie in anderen Antworten erwähnt, werden beim Löschen eines ungültigen Zeigers keine Destruktoren aufgerufen, was ein Problem sein kann. In diesem Sinne ist es nicht "sicher".
Es gibt keinen guten Grund, das, was Sie tun, so zu tun, wie Sie es tun. Wenn Sie Ihre eigenen Freigabefunktionen schreiben möchten, können Sie Funktionsvorlagen verwenden, um Funktionen mit dem richtigen Typ zu generieren. Ein guter Grund dafür ist die Generierung von Pool-Allokatoren, die für bestimmte Typen äußerst effizient sein können.
Wie in anderen Antworten erwähnt, ist dies ein undefiniertes Verhalten in C ++. Im Allgemeinen ist es gut, undefiniertes Verhalten zu vermeiden, obwohl das Thema selbst komplex und voller widersprüchlicher Meinungen ist.
quelle
sizeof(T*) == sizeof(U*)
für alleT,U
darauf hindeutet, dass es möglich sein sollte, eine nicht vorlagenbasierte,void *
auf Garbage Collector basierende Implementierung zu haben. Aber dann, wenn der GC tatsächlich einen Zeiger löschen / freigeben muss, stellt sich genau diese Frage. Damit es funktioniert, benötigen Sie entweder Lambda-Funktions-Destruktor-Wrapper (urgh) oder eine Art dynamisches "Typ als Daten" -Ding, das ein Hin- und Herwechseln zwischen einem Typ und etwas Speicherbarem ermöglicht.Das Löschen über einen ungültigen Zeiger ist im C ++ - Standard nicht definiert - siehe Abschnitt 5.3.5 / 3:
Und seine Fußnote:
.
quelle
NULL
einem Unterschied für die Verwaltung des Anwendungsspeichers.Es ist keine gute Idee und nichts, was Sie in C ++ tun würden. Sie verlieren Ihre Typinformationen ohne Grund.
Ihr Destruktor wird nicht für die Objekte in Ihrem Array aufgerufen, die Sie löschen, wenn Sie ihn für nicht primitive Typen aufrufen.
Sie sollten stattdessen new / delete überschreiben.
Durch das Löschen der Leere * wird Ihr Speicher wahrscheinlich zufällig korrekt freigegeben, aber es ist falsch, da die Ergebnisse undefiniert sind.
Wenn Sie aus einem mir unbekannten Grund Ihren Zeiger in einer Leere * speichern müssen, um ihn freizugeben, sollten Sie malloc und free verwenden.
quelle
Das Löschen eines ungültigen Zeigers ist gefährlich, da Destruktoren nicht für den Wert aufgerufen werden, auf den er tatsächlich zeigt. Dies kann zu Speicher- / Ressourcenlecks in Ihrer Anwendung führen.
quelle
Die Frage macht keinen Sinn. Ihre Verwirrung kann teilweise auf die schlampige Sprache zurückzuführen sein, mit der Menschen häufig sprechen
delete
:Sie verwenden
delete
, um ein Objekt zu zerstören , das dynamisch zugewiesen wurde. Dazu bilden Sie einen Löschausdruck mit einem Zeiger auf dieses Objekt . Sie "löschen niemals einen Zeiger". Was Sie wirklich tun, ist "ein Objekt löschen, das durch seine Adresse identifiziert wird".Jetzt sehen wir, warum die Frage keinen Sinn ergibt: Ein ungültiger Zeiger ist nicht die "Adresse eines Objekts". Es ist nur eine Adresse ohne Semantik. Es kann von der Adresse eines tatsächlichen Objekts stammen, aber diese Informationen gehen verloren, weil sie im Typ des ursprünglichen Zeigers codiert wurden . Die einzige Möglichkeit, einen Objektzeiger wiederherzustellen, besteht darin, den leeren Zeiger auf einen Objektzeiger zurückzusetzen (für den der Autor wissen muss, was der Zeiger bedeutet).
void
selbst ist ein unvollständiger Typ und daher niemals der Typ eines Objekts, und ein ungültiger Zeiger kann niemals zur Identifizierung eines Objekts verwendet werden. (Objekte werden gemeinsam anhand ihres Typs und ihrer Adresse identifiziert.)quelle
delete
kann ein Nullzeigerwert, ein Zeiger auf ein Nicht-Array-Objekt sein, das durch einen vorherigen neuen Ausdruck erstellt wurde , oder ein Zeiger auf a Unterobjekt, das eine Basisklasse eines solchen Objekts darstellt. Wenn nicht, ist das Verhalten undefiniert. " Wenn also ein Compiler Ihren Code ohne Diagnose akzeptiert, ist dies nichts anderes als ein Fehler im Compiler ...delete void_pointer
. Es ist undefiniertes Verhalten. Programmierer sollten niemals undefiniertes Verhalten aufrufen, selbst wenn die Antwort das zu tun scheint, was der Programmierer wollte.Wenn Sie dies wirklich tun müssen, warum nicht den Mittelsmann (die
new
unddelete
Operatoren) ausschneiden und den globalenoperator new
undoperator delete
direkten Anruf tätigen ? (Natürlich, wenn Sie zum Instrumente sind zu versuchen , dienew
unddelete
Betreiber, sollten Sie tatsächlich neu zu implementierenoperator new
undoperator delete
.)Beachten Sie, dass im Gegensatz zu
malloc()
,operator new
wirftstd::bad_alloc
auf Fehler (oder ruft die,new_handler
wenn eine registriert ist).quelle
Weil char keine spezielle Destruktorlogik hat. DAS wird nicht funktionieren.
Der d'ctor wird nicht gerufen.
quelle
Wenn Sie void * verwenden möchten, warum verwenden Sie nicht einfach malloc / free? Neu / Löschen ist mehr als nur Speicherverwaltung. Grundsätzlich ruft new / delete einen Konstruktor / Destruktor auf und es sind noch weitere Dinge im Gange. Wenn Sie nur integrierte Typen (wie char *) verwenden und diese durch void * löschen, funktioniert dies, wird jedoch nicht empfohlen. Unter dem Strich verwenden Sie malloc / free, wenn Sie void * verwenden möchten. Andernfalls können Sie Vorlagenfunktionen verwenden.
quelle
Viele Leute haben bereits kommentiert, dass es nicht sicher ist, einen ungültigen Zeiger zu löschen. Ich stimme dem zu, aber ich wollte auch hinzufügen, dass Sie, wenn Sie mit leeren Zeigern arbeiten, um zusammenhängende Arrays oder ähnliches zuzuweisen, dies tun können,
new
damit Siedelete
sicher (mit, ahem) verwenden können ein wenig zusätzliche Arbeit). Dies erfolgt durch Zuweisen eines leeren Zeigers zum Speicherbereich (als "Arena" bezeichnet) und anschließendes Bereitstellen des Zeigers zur Arena für einen neuen. Siehe diesen Abschnitt in den C ++ - FAQ . Dies ist ein gängiger Ansatz zur Implementierung von Speicherpools in C ++.quelle
Es gibt kaum einen Grund dafür.
Wenn Sie den Typ der Daten nicht kennen und nur wissen, dass dies der
void*
Fall ist, sollten Sie diese Daten zunächst nur als typenlosen Blob aus Binärdaten (unsigned char*
) behandeln undmalloc
/ verwenden,free
um damit umzugehen . Dies ist manchmal für Dinge wie Wellenformdaten und dergleichen erforderlich, bei denen Sievoid*
Zeiger auf C apis weitergeben müssen. Das ist gut.Wenn Sie zu tun , die Art der Daten wissen (dh es hat eine Ctor / dtor), aber aus irgendeinem Grund beendet man mit einem up -
void*
Zeiger (aus welchem Grund Sie haben) , dann sollten Sie es wirklich zurückgeworfen auf die Art wissen Sie es sei und rufedelete
es an.quelle
Ich habe void * (auch bekannt als unbekannte Typen) in meinem Framework für die Zeit der Code-Reflektion und anderer mehrdeutiger Heldentaten verwendet. Bisher hatte ich keine Probleme (Speicherverlust, Zugriffsverletzungen usw.) von Compilern. Nur Warnungen, da der Vorgang nicht dem Standard entspricht.
Es ist durchaus sinnvoll, ein Unbekanntes zu löschen (void *). Stellen Sie einfach sicher, dass der Zeiger diesen Richtlinien entspricht, da er sonst möglicherweise keinen Sinn mehr ergibt:
1) Der unbekannte Zeiger darf nicht auf einen Typ zeigen, der einen trivialen Dekonstruktor hat. Wenn er als unbekannter Zeiger umgewandelt wird, sollte er NIEMALS GELÖSCHT werden. Löschen Sie den unbekannten Zeiger erst, nachdem Sie ihn wieder in den ORIGINAL-Typ umgewandelt haben.
2) Wird die Instanz als unbekannter Zeiger im Stapel- oder Heap-gebundenen Speicher referenziert? Wenn der unbekannte Zeiger auf eine Instanz auf dem Stapel verweist, sollte er NIEMALS GELÖSCHT werden!
3) Sind Sie zu 100% sicher, dass der unbekannte Zeiger ein gültiger Speicherbereich ist? Nein, dann sollte es NIEMALS GELÖSCHT werden!
Insgesamt kann mit einem unbekannten (void *) Zeigertyp nur sehr wenig direkte Arbeit geleistet werden. Indirekt ist die Leere * jedoch ein großer Vorteil für C ++ - Entwickler, auf den sie sich verlassen können, wenn Datenmehrdeutigkeiten erforderlich sind.
quelle
Wenn Sie nur einen Puffer möchten, verwenden Sie malloc / free. Wenn Sie new / delete verwenden müssen, ziehen Sie eine triviale Wrapper-Klasse in Betracht:
quelle
Für den speziellen Fall von char.
char ist ein intrinsischer Typ, der keinen speziellen Destruktor hat. Die undichten Argumente sind also umstritten.
sizeof (char) ist normalerweise eins, daher gibt es auch kein Ausrichtungsargument. Bei seltenen Plattformen, bei denen die Größe von (char) nicht eins ist, weisen sie ausreichend Speicher für ihr char zu. Das Ausrichtungsargument ist also auch umstritten.
malloc / free wäre in diesem Fall schneller. Aber Sie verlieren std :: bad_alloc und müssen das Ergebnis von malloc überprüfen. Das Aufrufen der globalen Operatoren new und delete ist möglicherweise besser, da es den Middle Man umgeht.
quelle
new
das Werfen tatsächlich definiert ist. Das ist nicht wahr. Es ist abhängig von Compiler und Compiler-Switch. Siehe zum Beispiel MSVC2019-/GX[-] enable C++ EH (same as /EHsc)
Switches. Auch auf eingebetteten Systemen zahlen viele die Leistungssteuern für C ++ - Ausnahmen nicht. Der Satz, der mit "Aber Sie verlieren std :: bad_alloc ..." beginnt, ist fraglich.