Detectando uso indevido de delete [] vs. delete em tempo de compilação

19

Gostaria de saber se é possível detectar o deleteerro comentado abaixo em tempo de compilação? Especialmente, eu gostaria de ouvir sobre o compilador g ++.

ClassTypeA *abc_ptr = new ClassTypeA[100];  
abc_ptr[10].data_ = 1;  
delete abc_ptr; // error, should be delete []  
SebGR
fonte
7
Você não deve ligar para excluir manualmente de qualquer maneira.
Martin York
9
@LokiAstari Você realmente achou esse comentário útil?
James
5
@ James: Sim. A chave é "manualmente".
Martin York
Às vezes, para dar cumprimento à presente envolveria reescrever um monte de código legado
Nick Keighley
Use std::unique_ptr<ClassTypeA[]>e então você não precisa.
user253751

Respostas:

6

Em geral, o compilador não pode detectar esses erros. Exemplo: suponha que o construtor de alguma classe aloque algum membro de dados usando new TypeName[], mas o destruidor usa erroneamente em deletevez de delete[]. Se o construtor e o destruidor são definidos em unidades de compilação separadas, como o compilador deve saber ao compilar o arquivo que define o destruidor que o uso é inconsistente com o do arquivo compilado separadamente que define o construtor?

Com relação aos compiladores GNU, isso não acontece. Como observado acima, não pode ser feito no caso geral. Um compilador não precisa detectar erros de exclusão / exclusão diferentes porque esse é um comportamento indefinido. UB é o cartão "saia da cadeia" do fornecedor do compilador.

Ferramentas como o valgrind podem detectar esses tipos de incompatibilidades novas / excluídas, mas o fazem em tempo de execução. Pode haver uma ferramenta de análise estática que analisa todos os arquivos de origem que eventualmente serão compilados para formar um executável, mas eu não utilizo nenhuma ferramenta de análise estática que detecta esse tipo de erro.

David Hammen
fonte
Eu usei uma ferramenta de análise estática chamada Parasoft que definitivamente tem uma regra para esse cenário específico. É executado em todos os arquivos em um projeto específico (se tiver sido configurado corretamente). Dito isto, não tenho certeza de como ele lida com cenários como o comentário de Pete Kirkham na resposta de Kilian Foth.
achou
28

Você pode usar as classes RAII apropriadas para delete. Esta é a única maneira segura de fazê-lo, e esse erro é apenas um dos muitos que você encontrará chamando a deletesi mesmo.

Sempre use classes para gerenciar recursos dinâmicos da vida útil, e o sistema de tipos aplicará a destruição correta dos recursos.

Edit: "E se você estiver auditando o código e não puder alterá-lo?" Você está fodido.

DeadMG
fonte
18
-1 porque isso realmente não responde à pergunta.
Mason Wheeler
2
A única maneira de detectar a incompatibilidade é usar o sistema de tipos, que envolve o uso de classes RAII.
131313 DeadMG
9
... isso faz ainda menos sentido. O que o uso das classes RAII - um mecanismo de tempo de execução - tem a ver com informações do sistema do tipo estático que o compilador conhece no momento da compilação?
Mason Wheeler
6
@MasonWheeler vê boost :: shared_ptr e boost :: shared_array como exemplos. Destruir o shared_ptr exclui o objeto, destruindo o shared_array delete [] da matriz. Você não pode atribuir um shared_array a shared_ptr, portanto - desde que você não construa um shared_ptr com uma matriz em primeiro lugar - o sistema de tipos impede que a exclusão incorreta seja usada.
Pete Kirkham
4
Normalmente, uma resposta como essa é mais desagradável do que útil. No entanto, neste caso, é realmente verdade. Ele está procurando pela imposição do compilador de um erro comum e o uso do RAII evita adequadamente esse estilo de erro, dando a ele exatamente o que ele deseja. +1
riwalk 13/02/2013
10

Este erro em particular - sim. Esse tipo de erro geralmente: infelizmente não! Isso envolveria prever o fluxo de execução sem realmente executá-lo, e isso não é possível para programas arbitrários. (É por isso que a maioria dos compiladores nem tenta detectar casos simples como o seu exemplo.)

Portanto, a resposta do DeadMG é a apropriada: não tente acertar prestando atenção - a atenção humana é falível. Use os meios fornecidos pelo idioma e deixe o computador prestar atenção.

Kilian Foth
fonte
Como isso requer prever o fluxo de execução? Isso me parece um conhecimento puramente estático em tempo de compilação; o sistema de tipos do compilador sabe o que é uma matriz e o que não é.
Mason Wheeler
Mesmo na presença de elencos? Desculpe, se eu entendi errado, vou excluir a resposta.
22713 Kilian Foth
12
@MasonWheeler, o tipo estático de abc_ptr é ClassTypeA*para que você possa inserir uma linha entre o novo e o delete if ( rand() % 2 == 1 ) abc_ptr = new ClassTypeA;Nada no sistema de tipos estáticos está mostrando se abc_ptraponta para uma matriz ou objeto dinâmico ou parte de outro objeto ou matriz.
Pete Kirkham
...Oh, certo. Estou tão acostumado a trabalhar com linguagens com tipos de matrizes reais que continuo esquecendo como as coisas acabaram no C-land. :(
Mason Wheeler
1
@Pete Kirkham, @Mason Wheeler: Mas, ainda assim, o tempo de execução deve ver quantos objetos estão armazenados no endereço indicado abc_ptr, caso contrário, como ele poderia desalocar a quantidade certa de memória? Portanto, o tempo de execução sabe quantos objetos devem ser desalocados.
Giorgio
4

O caso trivial que você mostra pode ser detectado em tempo de compilação, porque a instanciação e a destruição do objeto estão no mesmo escopo. Em geral, a exclusão não está no mesmo escopo ou no mesmo arquivo de origem que a instanciação. E o tipo de um ponteiro C ++ não carrega informações sobre se ele faz referência a um único objeto de seu tipo ou matriz, sem falar no esquema de alocação. Portanto, não é possível diagnosticar isso em tempo de compilação em geral.

Por que não diagnosticar os casos especiais possíveis?

No C ++, já existem ferramentas para lidar com o vazamento de recursos dinâmicos vinculados a escopos, como ponteiros inteligentes e matrizes de nível superior ( std::vector).

Mesmo se você usar o deletesabor correto , seu código ainda não será seguro. Se o código entre new[]e delete[]termina com uma saída dinâmica, a exclusão nunca é executada.

No que diz respeito à detecção em tempo de execução, a Valgrindferramenta faz um bom trabalho ao detectar isso em tempo de execução. Ver:

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

Obviamente, o Valgrind não é executado em todas as plataformas, e nem sempre é prático ou possível reproduzir todas as situações de tempo de execução na ferramenta.

Kaz
fonte
você diz que esse caso trivial pode ser detectado em tempo de compilação. Você poderia me dizer qual comando de compilação você usa para conseguir isso?
SebGR
"pode ​​ser detectado em tempo de compilação" aqui significa que é fácil de implementar em um compilador, não que o g ++ o possua. Um compilador tem toda a vida útil do identificador ao processar esse escopo e pode propagar as informações de alocação como um atributo semântico vinculado à sintaxe.
Kaz
-3

Alguns exemplos triviais de detecção em tempo de compilação / tempo de análise estática:

Em um host RHEL7 com 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

Com clang++ 3.7.1no 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.

O Clang Static Analyzer também pode detectar quando std::unique_ptrnão é passado<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.

Atualize abaixo com um link para o trabalho que adicionou isso ao clang, aos testes e a um bug que encontrei.

Isso foi adicionado ao clang com reviews.llvm.org/D4661 - "Detectar incompatibilidades 'novas' e 'excluir' usos" .

Os testes estão em test / Analysis / MismatchedDeallocator-checker-test.mm

Encontrei este bug aberto - bugs.llvm.org/show_bug.cgi?id=24819

thatsafunnyname
fonte
Ninguém dúvidas que você pode encontrar um analisador estático que detecta um específico uso errado, em vez de um que detecta todos os usos errados (e, esperamos erros nenhum direito usos)
Caleth