Détection d'une mauvaise utilisation de la suppression [] par rapport à la suppression lors de la compilation

19

Je voudrais savoir s'il est possible de détecter l' deleteerreur commentée ci-dessous au moment de la compilation? Surtout, j'aimerais entendre parler du compilateur g ++.

ClassTypeA *abc_ptr = new ClassTypeA[100];  
abc_ptr[10].data_ = 1;  
delete abc_ptr; // error, should be delete []  
SebGR
la source
7
De toute façon, vous ne devriez pas appeler la suppression manuellement.
Martin York
9
@LokiAstari Avez-vous réellement pensé que ce commentaire était utile?
James
5
@James: Oui. La clé est "manuellement".
Martin York
Parfois, pour se conformer à cela, il faudrait réécrire beaucoup de code hérité
Nick Keighley
Utilisez std::unique_ptr<ClassTypeA[]>et vous n'en avez pas besoin.
user253751

Réponses:

6

En général, le compilateur ne peut pas détecter de telles erreurs. Exemple: supposons que le constructeur d'une classe alloue un membre de données à l'aide new TypeName[], mais que le destructeur utilise par erreur deleteau lieu dedelete[] . Si le constructeur et le destructeur sont définis dans des unités de compilation distinctes, comment le compilateur doit-il savoir lors de la compilation du fichier qui définit le destructeur que l'utilisation est incompatible avec celle du fichier compilé séparément qui définit le constructeur?

En ce qui concerne les compilateurs GNU, ce n'est pas le cas. Comme indiqué ci-dessus, il ne peut pas le faire dans le cas général. Un compilateur n'a pas à détecter de telles erreurs de nouvelle / suppression incompatibles car il s'agit d'un comportement non défini. UB est la carte «sortir de prison» du vendeur du compilateur.

Des outils tels que valgrind peuvent détecter et détectent ces types de disparités nouvelles / supprimées, mais le font au moment de l'exécution. Il pourrait y avoir un outil d'analyse statique qui examine tous les fichiers source qui seront éventuellement compilés pour former un exécutable, mais je ne connais aucun outil d'analyse statique qui détecte ce type d'erreur.

David Hammen
la source
J'ai utilisé un outil d'analyse statique appelé Parasoft qui a définitivement une règle pour ce scénario spécifique. Il s'exécute sur tous les fichiers d'un projet particulier (s'il a été configuré correctement). Cela étant dit, je ne suis pas certain de la façon dont il gère les scénarios tels que le commentaire de Pete Kirkham sur la réponse de Kilian Foth.
Velociraptors
28

Vous pouvez utiliser les classes RAII appropriées pour delete. C'est le seul moyen sûr de le faire, et cette erreur n'est que l'une des très, très nombreuses que vous rencontrerez en deletevous appelant .

Utilisez toujours des classes pour gérer les ressources dynamiques à vie, et le système de types appliquera la destruction des ressources correcte.

Edit: "Et si vous auditez le code et ne pouvez pas le changer?" Tu es foutu.

DeadMG
la source
18
-1 car cela ne répond pas réellement à la question.
Mason Wheeler
2
La seule façon de détecter la non-concordance est d'utiliser le système de types, ce qui implique l'utilisation de classes RAII.
DeadMG
9
... cela a encore moins de sens. Qu'est-ce que l'utilisation des classes RAII - un mécanisme d'exécution - a à voir avec les informations système de type statique que le compilateur connaît au moment de la compilation?
Mason Wheeler
6
@MasonWheeler voir boost :: shared_ptr et boost :: shared_array comme exemples. La destruction de shared_ptr supprime l'objet, la destruction de shared_array supprime [] s le tableau. Vous ne pouvez pas affecter un tableau shared_array à un shared_ptr, donc - tant que vous ne construisez pas un shared_ptr avec un tableau en premier lieu - le système de type empêche la mauvaise suppression d'être utilisée.
Pete Kirkham
4
Normalement, une réponse comme celle-ci est plus désagréable qu'utile. Cependant, dans ce cas, c'est vrai. Il recherche l'application par le compilateur d'une erreur courante et l'utilisation correcte de RAII empêche ce style d'erreur, lui donnant ainsi exactement ce qu'il veut. +1
riwalk
10

Cette erreur particulière - oui. Ce genre d'erreur en général: malheureusement non! Cela impliquerait de prévoir le flux d'exécution sans l'exécuter réellement, et ce n'est pas possible pour les programmes arbitraires. (C'est pourquoi la plupart des compilateurs n'essaient même pas de détecter des cas simples comme votre exemple.)

Par conséquent, la réponse de DeadMG est la bonne: n'essayez pas de bien faire les choses en faisant attention - l'attention humaine est faillible. Utilisez les moyens fournis par la langue et laissez l'ordinateur faire attention.

Kilian Foth
la source
Comment cela nécessite-t-il de prévoir le flux d'exécution? Cela ressemble à une connaissance purement statique à la compilation; le système de type du compilateur sait ce qu'est un tableau et ce qui ne l'est pas.
Mason Wheeler
Même en présence de plâtres? Désolé, si je me trompe, je supprimerai la réponse.
Kilian Foth
12
@MasonWheeler le type statique de abc_ptr est ClassTypeA*donc vous pouvez insérer une ligne entre le nouveau et le supprimer if ( rand() % 2 == 1 ) abc_ptr = new ClassTypeA;Rien dans le système de type statique ne montre si les abc_ptrpoints pointent vers un tableau ou un objet dynamique ou à mi-chemin dans un autre objet ou tableau.
Pete Kirkham
... oh, c'est vrai. Je suis tellement habitué à travailler avec des langages avec de vrais types de tableaux que j'oublie toujours à quel point c'est foutu en C-land. :(
Mason Wheeler
1
@Pete Kirkham, @Mason Wheeler: Mais quand même, le runtime devrait voir combien d'objets sont stockés à l'adresse pointée par abc_ptr, sinon comment pourrait-il être capable de désallouer la bonne quantité de mémoire? Le runtime sait donc combien d'objets doivent être désalloués.
Giorgio
4

Le cas trivial que vous montrez peut être détecté au moment de la compilation, car l'instanciation et la destruction de l'objet ont la même portée. En général, la suppression n'a pas la même portée, ni même le même fichier source, que l'instanciation. Et le type d'un pointeur C ++ ne contient pas d'informations sur s'il fait référence à un seul objet de son type ou à un tableau, sans parler du schéma d'allocation. Il n'est donc pas possible de diagnostiquer cela au moment de la compilation en général.

Pourquoi ne pas diagnostiquer les cas particuliers qui sont possibles?

En C ++, il existe déjà des outils pour gérer les fuites de ressources dynamiques liées aux étendues, à savoir les pointeurs intelligents et les tableaux de niveau supérieur ( std::vector).

Même si vous utilisez la bonne deletesaveur, votre code n'est toujours pas sûr d'exception. Si le code entre le new[]et se delete[]termine par une sortie dynamique, la suppression ne s'exécute jamais.

En ce qui concerne la détection au moment de l'exécution, l' Valgrindoutil fait un bon travail de détection lors de l'exécution. Regarder:

==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)

Bien sûr, Valgrind ne fonctionne pas sur toutes les plates-formes, et il n'est pas toujours pratique ou possible de reproduire toutes les situations d'exécution sous l'outil.

Kaz
la source
vous dites que ce cas trivial peut être détecté au moment de la compilation. Pourriez-vous s'il vous plaît me dire quelle commande de compilation vous utilisez pour y parvenir?
SebGR
"peut être détecté au moment de la compilation" signifie ici qu'il est facile à implémenter dans un compilateur, pas que g ++ l'ait. Un compilateur a toute la durée de vie de l'identifiant à sa portée lors du traitement de cette étendue, et peut propager les informations d'allocation en tant qu'attribut sémantique lié à la syntaxe.
Kaz
-3

Quelques exemples triviaux de détection au moment de la compilation / de l'analyse statique:

Sur un hôte RHEL7 avec 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

Avec clang++ 3.7.1sur 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.

L'analyseur statique Clang peut également détecter quand il std::unique_ptrn'est pas passé<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.

Mettez à jour ci-dessous avec un lien vers le travail qui l'a ajouté à clang, les tests et un bug que j'ai trouvé.

Cela a été ajouté à clang avec reviews.llvm.org/D4661 - «Détecter les utilisations« nouvelles »et« supprimer »incompatibles» .

Les tests sont dans test / Analysis / MismatchedDeallocator-checker-test.mm

J'ai trouvé ce bogue ouvert - bugs.llvm.org/show_bug.cgi?id=24819

thatsafunnyname
la source
Personne ne doute que vous pouvez trouver un analyseur statique qui détecte un mauvais usage spécifique , au lieu de cela qui détecte tous les mauvais usages (et, espérons-le, ne commet aucune erreur )
Caleth