Erkennen des Missbrauchs von delete [] im Vergleich zu delete beim Kompilieren

19

Ich möchte wissen, ob es möglich ist, den deleteunten angegebenen Fehler beim Kompilieren zu erkennen . Insbesondere würde ich gerne etwas über den g ++ Compiler erfahren.

ClassTypeA *abc_ptr = new ClassTypeA[100];  
abc_ptr[10].data_ = 1;  
delete abc_ptr; // error, should be delete []  
SebGR
quelle
7
Sie sollten delete sowieso nicht manuell aufrufen.
Martin York
9
@LokiAstari Hast du eigentlich gedacht, dass dieser Kommentar hilfreich ist?
James
5
@ James: Ja. Der Schlüssel ist "manuell".
Martin York
Manchmal musste man eine Menge alten Code umschreiben, um dies zu erfüllen
Nick Keighley,
Verwenden Sie std::unique_ptr<ClassTypeA[]>und dann müssen Sie nicht.
user253751

Antworten:

6

Im Allgemeinen kann der Compiler solche Fehler nicht erkennen. Beispiel: Angenommen, der Konstruktor für eine Klasse new TypeName[]weist ein Datenelement mit zu , der Destruktor verwendet jedoch fälschlicherweise deletestatt delete[]. Wenn der Konstruktor und der Destruktor in separaten Kompilierungseinheiten definiert sind, wie soll der Compiler beim Kompilieren der Datei, die den Destruktor definiert, wissen, dass die Verwendung nicht mit der in der separat kompilierten Datei übereinstimmt, die den Konstruktor definiert?

In Bezug auf die GNU-Compiler ist dies nicht der Fall. Wie oben erwähnt, ist dies im allgemeinen Fall nicht möglich. Ein Compiler muss solche nicht übereinstimmenden neuen / gelöschten Fehler nicht erkennen, da dies ein undefiniertes Verhalten ist. UB ist die "Get out of Jail Free" -Karte des Compiler-Anbieters.

Tools wie valgrind können diese Art von Neu- / Löschfehlanpassungen erkennen und erkennen, dies jedoch zur Laufzeit. Möglicherweise gibt es ein statisches Analysetool, das alle Quelldateien untersucht, die schließlich zu einer ausführbaren Datei kompiliert werden, aber ich kenne kein solches statisches Analysetool, das diese Art von Fehler erkennt.

David Hammen
quelle
Ich habe ein statisches Analysetool namens Parasoft verwendet , das definitiv eine Regel für dieses spezielle Szenario enthält. Es wird für alle Dateien in einem bestimmten Projekt ausgeführt (sofern es richtig konfiguriert wurde). Trotzdem bin ich mir nicht sicher, wie gut es mit Szenarien wie Pete Kirkhams Kommentar zu Kilian Foths Antwort umgeht.
Velociraptors
28

Sie können die entsprechenden RAII-Klassen verwenden, um delete. Dies ist der einzig sichere Weg, und dieser Fehler ist nur einer von vielen, denen Sie begegnen werden, wenn Sie sich deleteselbst anrufen .

Verwenden Sie immer Klassen zum Verwalten von Ressourcen mit dynamischer Lebensdauer, und das Typsystem erzwingt die korrekte Ressourcenzerstörung.

Bearbeiten: "Was ist, wenn Sie den Code prüfen und ihn nicht ändern können?" Du bist gefickt.

DeadMG
quelle
18
-1 weil dies die Frage nicht beantwortet.
Mason Wheeler
2
Die einzige Möglichkeit, die Nichtübereinstimmung festzustellen, besteht in der Verwendung des Typsystems, bei dem RAII-Klassen verwendet werden.
DeadMG
9
... das macht noch weniger Sinn. Was hat die Verwendung von RAII-Klassen - einem Laufzeitmechanismus - mit statischen Typsysteminformationen zu tun, die dem Compiler zur Kompilierungszeit bekannt sind?
Mason Wheeler
6
@ MasonWheeler siehe boost :: shared_ptr und boost :: shared_array als Beispiele. Durch das Zerstören von shared_ptr wird das Objekt gelöscht, durch das Zerstören von shared_array wird das Array gelöscht. Sie können einem shared_ptr kein shared_array zuweisen. Solange Sie also überhaupt kein shared_ptr mit einem Array erstellen, verhindert das Typsystem, dass das falsche Löschen verwendet wird.
Pete Kirkham
4
Normalerweise ist eine Antwort wie diese eher abscheulich als hilfreich. In diesem Fall ist dies jedoch tatsächlich der Fall. Er ist auf der Suche nach der Durchsetzung eines häufigen Fehlers durch den Compiler, und die ordnungsgemäße Verwendung von RAII verhindert diese Art von Fehlern und gibt ihm genau das, was er will. +1
Riwalk
10

Dieser besondere Fehler - ja. Diese Art von Fehler allgemein: leider nein! Das würde bedeuten, den Ausführungsfluss vorherzusagen, ohne ihn tatsächlich auszuführen, und das ist für beliebige Programme nicht möglich. (Deshalb versuchen die meisten Compiler nicht einmal, einfache Fälle wie Ihr Beispiel zu erkennen.)

Daher ist die Antwort von DeadMG die richtige: Versuchen Sie nicht, es richtig zu machen, indem Sie darauf achten - menschliche Aufmerksamkeit ist fehlbar. Verwenden Sie die von der Sprache bereitgestellten Mittel und lassen Sie den Computer darauf achten.

Kilian Foth
quelle
Wie muss der Ausführungsfluss vorhergesagt werden? Dies scheint mir ein rein statisches Wissen zur Kompilierungszeit zu sein. Das Typsystem des Compilers weiß, was ein Array ist und was nicht.
Mason Wheeler
Auch in Gegenwart von Abgüssen? Entschuldigung, wenn ich das falsch verstanden habe, lösche ich die Antwort.
Kilian Foth
12
@MasonWheeler Der statische Typ von abc_ptr ist ClassTypeA*so, dass Sie eine Linie zwischen dem neuen und dem Lösch- if ( rand() % 2 == 1 ) abc_ptr = new ClassTypeA;Typ einfügen können. Nichts im statischen Typsystem zeigt an, ob abc_ptrauf ein Array oder ein dynamisches Objekt oder einen Teil davon in ein anderes Objekt oder Array verweist.
Pete Kirkham
...Oh, richtig. Ich bin es so gewohnt, mit Sprachen mit echten Array-Typen zu arbeiten, dass ich immer wieder vergesse, wie durcheinander es in C-Land ist. :(
Mason Wheeler
1
@Pete Kirkham, @Mason Wheeler: Die Laufzeit sollte jedoch sehen, wie viele Objekte an der Adresse gespeichert sind, auf die von verwiesen wird abc_ptr. Wie kann sie sonst die richtige Speichermenge freigeben? Die Laufzeit weiß also, wie viele Objekte freigegeben werden müssen.
Giorgio
4

Der von Ihnen angezeigte triviale Fall kann zur Kompilierungszeit erkannt werden, da sich die Instanziierung und Zerstörung des Objekts im selben Bereich befinden. Im Allgemeinen liegt das Löschen nicht im selben Bereich oder sogar in derselben Quelldatei wie die Instanziierung. Der Typ eines C ++ - Zeigers enthält keine Informationen darüber, ob er auf ein einzelnes Objekt seines Typs oder auf ein Array verweist, geschweige denn auf das Zuordnungsschema. Daher ist es im Allgemeinen nicht möglich, dies beim Kompilieren zu diagnostizieren.

Warum nicht die möglichen Sonderfälle diagnostizieren?

In C ++ gibt es bereits Tools für den Umgang mit dem Verlust dynamischer Ressourcen, die an Bereiche gebunden sind, nämlich intelligente Zeiger und Arrays höherer Ebene ( std::vector).

Auch wenn Sie die richtige deleteVariante verwenden, ist Ihr Code nicht ausnahmesicher. Wenn der Code zwischen new[]und delete[]durch einen dynamischen Exit beendet wird, wird der Löschvorgang nie ausgeführt.

In Bezug auf die Laufzeiterkennung Valgrindleistet das Tool gute Arbeit, um dies zur Laufzeit zu erkennen. Uhr:

==26781== Command: ./a.out
==26781==
==26781== Mismatched free() / delete / delete []
==26781==    at 0x402ACFC: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==26781==    by 0x8048498: main (in /home/kaz/test/a.out)
==26781==  Address 0x4324028 is 0 bytes inside a block of size 80 alloc'd
==26781==    at 0x402B454: operator new[](unsigned int) (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==26781==    by 0x8048488: main (in /home/kaz/test/a.out)

Natürlich läuft Valgrind nicht auf allen Plattformen und es ist nicht immer praktisch oder möglich, alle Laufzeitsituationen unter dem Tool zu reproduzieren.

Kaz
quelle
Sie sagen, dass dieser triviale Fall beim Kompilieren erkannt werden kann. Können Sie mir bitte sagen, mit welchem ​​Kompilierungsbefehl Sie das erreichen?
13.
"kann zur Kompilierungszeit erkannt werden" bedeutet hier, dass es einfach in einen Compiler zu implementieren ist, nicht, dass es in g ++ vorhanden ist. Ein Compiler hat die gesamte Lebensdauer des Bezeichners im Griff, wenn er diesen Bereich verarbeitet, und kann die Zuordnungsinformationen als semantisches Attribut weitergeben, das an die Syntax gebunden ist.
Kaz
-3

Einige triviale Beispiele für die Erkennung während der Kompilierung / statischen Analyse:

Auf einem RHEL7-Host mit cppcheck 1.77 and 1.49

> cat test.cc
#include <memory>
int main(){char* buf = new char[10];delete buf;}

http://cppcheck.sourceforge.net/

> cppcheck -x c++ test.cc
Checking test.cc ...
[test.cc:2]: (error) Mismatching allocation and deallocation: buf

Mit clang++ 3.7.1auf RHEL7

> clang++ --analyze -x c++ test.cc
test.cc:2:37: warning: Memory allocated by 'new[]' should be deallocated by
'delete[]', not 'delete'
int main(){char* buf = new char[10];delete buf;}
                                    ^~~~~~~~~~
1 warning generated.

Der Clang Static Analyzer kann auch erkennen, wenn er std::unique_ptrnicht bestanden wird<char[]>

> cat test2.cc
#include <memory>
int main(){std::unique_ptr<char> buf(new char[10]);}

https://clang-analyzer.llvm.org/

> clang++ --analyze -x c++ -std=c++11 test2.cc
In file included from test2.cc:1:
In file included from /opt/rh/devtoolset-4/root/usr/lib/gcc/x86_64-redhat-linux/5.3.1/
../../../../include/c++/5.3.1/memory:81:
/opt/rh/devtoolset-4/root/usr/lib/gcc/x86_64-redhat-linux/5.3.1/
../../../../include/c++/5.3.1/bits/unique_ptr.h:76:2: 
warning: Memory allocated by
      'new[]' should be deallocated by 'delete[]', not 'delete'
        delete __ptr;
        ^~~~~~~~~~~~
1 warning generated.

Update unten mit einem Link zu der Arbeit, die dies hinzugefügt hat, zu den Tests und einem Fehler, den ich gefunden habe.

Dies wurde hinzugefügt, um mit "reviews.llvm.org/D4661 " zu klingen .

Tests befinden sich in test / Analysis / MismatchedDeallocator-checker-test.mm

Ich habe diesen offenen Bug gefunden - bugs.llvm.org/show_bug.cgi?id=24819

thatsafunnyname
quelle
Niemand bezweifelt, dass Sie einen statischen Analysator finden, der eine bestimmte falsche Verwendung erkennt , stattdessen einen, der alle falschen Verwendungen erkennt (und hoffentlich keine richtigen Verwendungen
verwechselt