it-swarm.com.de

Ist es sicher, einen leeren Zeiger zu löschen?

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 ptr vor dem Löschen in char* Umgewandelt werden?

87
An̲̳̳drew

Es kommt auf "sicher" an. Dies funktioniert normalerweise, weil Informationen zusammen mit dem Zeiger über 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 Begrenzungsmarken verwendet. (Viele tun es.)

Wie in anderen Antworten erwähnt, werden beim Löschen eines ungültigen Zeigers jedoch 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 ndefiniertes Verhalten in C++. Im Allgemeinen ist es gut, undefiniertes Verhalten zu vermeiden, obwohl das Thema selbst komplex und voller widersprüchlicher Meinungen ist.

22
Christopher

Das Löschen über einen leeren Zeiger ist im C++ Standard nicht definiert - siehe Abschnitt 5.3.5/3:

In der ersten Alternative (Objekt löschen) muss der statische Typ des Operanden eine Basisklasse des dynamischen Typs des Operanden sein, und der statische Typ muss einen virtuellen Destruktor haben, oder das Verhalten ist undefiniert . In 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 es keine Objekte vom Typ void gibt

.

137
anon

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.

Wenn Sie die Leere * löschen, wird Ihr Speicher wahrscheinlich zufällig korrekt freigegeben, dies ist jedoch falsch, da die Ergebnisse undefiniert sind.

Wenn Sie Ihren Zeiger aus einem mir unbekannten Grund in einer Lücke * speichern müssen, dann geben Sie ihn frei, sollten Sie malloc und free verwenden.

24
Brian R. Bondy

Das Löschen eines leeren Zeigers ist gefährlich, da Destruktoren nicht für den Wert aufgerufen werden, auf den sie tatsächlich zeigen. Dies kann zu Speicher-/Ressourcenlecks in Ihrer Anwendung führen.

13
JaredPar

Die Frage ergibt keinen Sinn. Ihre Verwirrung kann teilweise auf die schlampige Sprache zurückzuführen sein, die häufig mit delete verwendet wird:

Sie verwenden delete, um ein Objekt zu zerstören, das dynamisch zugewiesen wurde. Bilden Sie dazu einen Löschausdruck mit einem Zeiger auf das 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 Leerzeiger 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 Information geht verloren, weil sie in codiert wurde ) Typ des ursprünglichen Zeigers. Die einzige Möglichkeit, einen Objektzeiger wiederherzustellen, besteht darin, den ungültigen 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.)

7
Kerrek SB

Wenn Sie dies wirklich tun müssen, warum schneiden Sie nicht den mittleren Mann (die Operatoren new und delete) aus und rufen direkt die globalen Operatoren operator new Und operator delete Auf? (Wenn Sie versuchen, die Operatoren new und delete zu instrumentieren, müssen Sie natürlich operator new Und operator delete Erneut implementieren.)

void* my_alloc (size_t size)
{
   return ::operator new(size);
}

void my_free (void* ptr)
{
   ::operator delete(ptr);
}

Beachten Sie, dass operator new Im Gegensatz zu malloc()std::bad_alloc Bei einem Fehler auslöst (oder den new_handler Aufruft, wenn einer registriert ist).

5
bk1e

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.

5
radu

Viele Leute haben bereits kommentiert, dass es nicht sicher ist, einen leeren Zeiger zu löschen. Ich stimme dem zu, aber ich wollte auch hinzufügen, dass Sie, wenn Sie mit Leerzeigern arbeiten, um zusammenhängende Arrays oder ähnliches zuzuweisen, dies mit new tun können, damit Sie dazu in der Lage sind benutze delete sicher (mit, ähm, ein bisschen zusätzlicher Arbeit). Dies geschieht, indem dem Speicherbereich (der als "Arena" bezeichnet wird) ein Leerzeiger zugewiesen und dann der Zeiger der Arena an new übergeben wird. Siehe diesen Abschnitt in den C++ FAQ . Dies ist ein gängiger Ansatz zum Implementieren von Speicherpools in C++.

5
Paul Morie

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 weitere Dinge im Gange. Wenn Sie nur eingebaute Typen (wie char *) verwenden und diese durch void * löschen, würde dies funktionieren, es wird jedoch nicht empfohlen. Das Fazit lautet malloc/free, wenn Sie void * verwenden möchten. Andernfalls können Sie die 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);
}
5
young

Wenn Sie nur einen Puffer benötigen, verwenden Sie malloc/free. Wenn Sie new/delete verwenden müssen, ziehen Sie eine einfache 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;
0
Sanjaya R

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 Speicher zu, der für ihre Zeichen ausgerichtet ist. Das Alignment-Argument ist also ebenfalls 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 Operatoren "global new" und "delete" ist möglicherweise besser, da sie den Middle Man umgehen.

0
rxantos

Ich habe void * (auch als unbekannte Typen bezeichnet) in meinem Framework verwendet, um Code zu reflektieren, und bis jetzt hatte ich keine Probleme (Speicherverlust, Zugriffsverletzungen usw.) von Compilern. Nur Warnungen wegen nicht standardmäßiger Bedienung.

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 stapelgebundenen oder heapgebundenen Speicher referenziert? Wenn der unbekannte Zeiger auf eine Instanz auf dem Stapel verweist, sollte er NIE GELÖSCHT werden!

3) Sind Sie zu 100% sicher, dass der unbekannte Zeiger ein gültiger Speicherbereich ist? Nein, dann sollte es NIE GELÖSCHT werden!

Insgesamt gibt es sehr wenig direkte Arbeit, die mit einem unbekannten Zeigertyp (void *) ausgeführt werden kann. Indirekt ist die Lücke jedoch ein großer Vorteil für C++ - Entwickler, auf den sie sich verlassen können, wenn Datenmehrdeutigkeiten erforderlich sind.

0
zackery.fix

Es gibt kaum einen Grund, dies zu tun.

Erstens, wenn Sie das Typ der Daten nicht kennen und nur wissen, dass es sich um void* Handelt, sollten Sie diese Daten einfach als typenlos behandeln. blob von Binärdaten (unsigned char*), und verwenden Sie malloc/free, um damit umzugehen. Dies ist manchmal für Dinge wie Wellenformdaten und dergleichen erforderlich, bei denen Sie void* - Zeiger auf C apis übergeben müssen. Das ist gut.

Wenn Sie do den Typ der Daten kennen (dh, sie haben einen ctor/dtor), aber aus irgendeinem Grund einen void* Zeiger haben (aus welchem ​​Grund auch immer) - Dann sollten Sie es wirklich auf den Typ zurücksetzen, von dem Sie wissen, dass er es ist und delete aufrufen.

0
bobobobo