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*
?
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*
?
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.
sizeof(T*) == sizeof(U*)
für alle T,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:
Wenn sich der statische Typ des Operanden von seinem dynamischen Typ unterscheidet, muss der statische Typ in der ersten Alternative (Objekt löschen) eine Basisklasse des dynamischen Typs des Operanden sein und der statische Typ muss einen virtuellen Destruktor haben oder das Verhalten ist undefiniert . Bei der zweiten Alternative (Array löschen) ist das Verhalten undefiniert, wenn sich der dynamische Typ des zu löschenden Objekts von seinem statischen Typ unterscheidet.
Und seine Fußnote:
Dies bedeutet, dass ein Objekt nicht mit einem Zeiger vom Typ void * gelöscht werden kann, da keine Objekte vom Typ void vorhanden sind
.
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.
Die Frage macht keinen Sinn. Ihre Verwirrung kann teilweise auf die schlampige Sprache zurückzuführen sein, mit der Menschen häufig sprechendelete
:
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.)
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
und delete
Operatoren) ausschneiden und den globalen operator new
und operator delete
direkten Anruf tätigen ? (Natürlich, wenn Sie zum Instrumente sind zu versuchen , die new
und delete
Betreiber, sollten Sie tatsächlich neu zu implementieren operator new
und operator delete
.)
void* my_alloc (size_t size)
{
return ::operator new(size);
}
void my_free (void* ptr)
{
::operator delete(ptr);
}
Beachten Sie, dass im Gegensatz zu malloc()
, operator new
wirft std::bad_alloc
auf Fehler (oder ruft die, new_handler
wenn eine registriert ist).
Weil char keine spezielle Destruktorlogik hat. DAS wird nicht funktionieren.
class foo
{
~foo() { printf("huzza"); }
}
main()
{
foo * myFoo = new foo();
delete ((void*)foo);
}
Der d'ctor wird nicht gerufen.
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.
template<typename T>
T* my_alloc (size_t size)
{
return new T [size];
}
template<typename T>
void my_free (T* ptr)
{
delete [] ptr;
}
int main(void)
{
char* pChar = my_alloc<char>(10);
my_free(pChar);
}
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 Sie delete
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 ++.
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 und malloc
/ verwenden, free
um damit umzugehen . Dies ist manchmal für Dinge wie Wellenformdaten und dergleichen erforderlich, bei denen Sie void*
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 rufe delete
es an.
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.
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:
template<int size_ > struct size_buffer {
char data_[ size_];
operator void*() { return (void*)&data_; }
};
typedef sized_buffer<100> OpaqueBuffer; // logical description of your sized buffer
OpaqueBuffer* ptr = new OpaqueBuffer();
delete ptr;
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.
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.