Detectando mal uso de delete [] vs. delete en tiempo de compilación

19

¿Me gustaría saber si es posible detectar el deleteerror comentado a continuación en el momento de la compilación? Especialmente, me gustaría escuchar sobre el compilador de g ++.

ClassTypeA *abc_ptr = new ClassTypeA[100];  
abc_ptr[10].data_ = 1;  
delete abc_ptr; // error, should be delete []  
SebGR
fuente
77
No debería llamar a eliminar manualmente de todos modos.
Martin York
99
@LokiAstari ¿Realmente pensaste que ese comentario fue útil?
James
55
@ James: Sí. La clave es "manualmente".
Martin York
A veces, cumplir con esto implicaría reescribir una gran cantidad de código heredado
Nick Keighley,
Use std::unique_ptr<ClassTypeA[]>y luego no necesita hacerlo.
user253751

Respuestas:

6

En general, el compilador no puede detectar tales errores. Ejemplo: supongamos que el constructor de alguna clase asigna algún miembro de datos usando new TypeName[], pero el destructor usa erróneamente en deletelugar de delete[]. Si el constructor y el destructor se definen en unidades de compilación separadas, ¿cómo debe saber el compilador al compilar el archivo que define el destructor que el uso es inconsistente con el del archivo compilado por separado que define el constructor?

Con respecto a los compiladores de GNU, no lo hace. Como se señaló anteriormente, no puede hacerlo en el caso general. Un compilador no tiene que detectar tales errores nuevos / eliminar no coincidentes porque este es un comportamiento indefinido. UB es la tarjeta de "salir de la cárcel" del proveedor del compilador.

Herramientas como valgrind pueden detectar y detectan este tipo de desajustes nuevos / eliminar, pero lo hacen en tiempo de ejecución. Puede haber una herramienta de análisis estático que observe todos los archivos fuente que eventualmente se compilarán para formar un ejecutable, pero no conozco ninguna herramienta de análisis estático que detecte este tipo de error.

David Hammen
fuente
He usado una herramienta de análisis estático llamada Parasoft que definitivamente tiene una regla para este escenario específico. Se ejecuta en todos los archivos de un proyecto en particular (si se configuró correctamente). Dicho esto, no estoy seguro de qué tan bien maneja escenarios como el comentario de Pete Kirkham sobre la respuesta de Kilian Foth.
Velociraptors
28

Puede usar las clases RAII apropiadas para delete. Esta es la única forma segura de hacerlo, y este error es solo uno de los muchos que encontrarás llamándote a deleteti mismo.

Utilice siempre clases para administrar recursos dinámicos de por vida, y el sistema de tipos impondrá la destrucción correcta de recursos.

Editar: "¿Qué sucede si está auditando el código y no puede cambiarlo?" Estas jodido

DeadMG
fuente
18
-1 porque esto en realidad no responde la pregunta.
Mason Wheeler
2
La única forma de detectar la falta de coincidencia es utilizar el sistema de tipos, que implica el uso de clases RAII.
DeadMG
99
... eso tiene aún menos sentido. ¿Qué tiene que ver el uso de las clases RAII, un mecanismo de tiempo de ejecución, con la información del sistema de tipo estático que el compilador conoce en tiempo de compilación?
Mason Wheeler
66
@MasonWheeler vea boost :: shared_ptr y boost :: shared_array como ejemplos. La destrucción del shared_ptr elimina el objeto, destruyendo el shared_array delete [] s la matriz. No puede asignar un shared_array a shared_ptr, por lo tanto, siempre y cuando no construya un shared_ptr con una matriz en primer lugar, el sistema de tipos evita que se use la eliminación incorrecta.
Pete Kirkham
44
Normalmente, una respuesta como esta es más desagradable que útil. Sin embargo, en este caso, es realmente cierto. Está buscando la aplicación del compilador de un error común, y el uso de RAII evita adecuadamente este estilo de error, lo que le da exactamente lo que quiere. +1
caminata
10

Este error particular, sí. Este tipo de error generalmente: desafortunadamente, no! Eso implicaría predecir el flujo de ejecución sin ejecutarlo realmente, y eso no es posible para programas arbitrarios. (Es por eso que la mayoría de los compiladores ni siquiera intentan detectar casos simples como su ejemplo).

Por lo tanto, la respuesta de DeadMG es la adecuada: no intente hacerlo bien prestando atención: la atención humana es falible. Use los medios proporcionados por el idioma y deje que la computadora preste atención.

Kilian Foth
fuente
¿Cómo requiere esto predecir el flujo de ejecución? Esto me parece un conocimiento puramente estático, en tiempo de compilación; El sistema de tipos del compilador sabe qué es una matriz y qué no.
Mason Wheeler
¿Incluso en presencia de moldes? Lo siento, si me equivoqué, eliminaré la respuesta.
Kilian Foth
12
@MasonWheeler, el tipo estático de abc_ptr es ClassTypeA*para que pueda insertar una línea entre el nuevo y el borrado if ( rand() % 2 == 1 ) abc_ptr = new ClassTypeA;Nada en el sistema de tipo estático muestra si abc_ptrapunta a una matriz o un objeto dinámico o parcialmente en otro objeto o matriz.
Pete Kirkham
...Correcto. Estoy tan acostumbrado a trabajar con lenguajes con tipos de matrices reales que sigo olvidando cuán jodido está todo en C-land. :(
Mason Wheeler
1
@Pete Kirkham, @Mason Wheeler: Pero aún así, el tiempo de ejecución debería ver cuántos objetos están almacenados en la dirección señalada abc_ptr, de lo contrario, ¿cómo podría ser capaz de desasignar la cantidad correcta de memoria? Por lo tanto, el tiempo de ejecución sabe cuántos objetos se deben desasignar.
Giorgio
4

El caso trivial que muestra puede detectarse en tiempo de compilación, porque la creación de instancias y la destrucción del objeto están en el mismo ámbito. En general, la eliminación no está en el mismo alcance, o incluso en el mismo archivo fuente, que la instanciación. Y el tipo de puntero de C ++ no contiene información sobre si hace referencia a un solo objeto de su tipo o una matriz, y mucho menos el esquema de asignación. Por lo tanto, no es posible diagnosticar esto en tiempo de compilación en general.

¿Por qué no diagnosticar los casos especiales que son posibles?

En C ++ ya hay herramientas para lidiar con la fuga de recursos dinámicos que están vinculados a ámbitos, a saber, punteros inteligentes y matrices de nivel superior ( std::vector).

Incluso si usa el deletesabor correcto , su código aún no es salvo para excepciones. Si el código entre new[]y delete[]termina con una salida dinámica, la eliminación nunca se ejecuta.

En cuanto a la detección en tiempo de ejecución, la Valgrindherramienta hace un buen trabajo al detectar esto en tiempo de ejecución. Reloj:

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

Por supuesto, Valgrind no se ejecuta en todas las plataformas, y no siempre es práctico o posible reproducir todas las situaciones de tiempo de ejecución bajo la herramienta.

Kaz
fuente
Usted dice que este caso trivial se puede detectar en tiempo de compilación. ¿Podría decirme qué comando de compilación utiliza para lograr eso?
SebGR
"se puede detectar en tiempo de compilación" aquí significa que es fácil de implementar en un compilador, no que g ++ lo tenga. Un compilador tiene toda la vida útil del identificador al procesar ese alcance y puede propagar la información de asignación como un atributo semántico vinculado a la sintaxis.
Kaz
-3

Algunos ejemplos triviales de detección en tiempo de compilación / tiempo de análisis estático:

En un host RHEL7 con 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

Con clang++ 3.7.1en 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.

El analizador estático de Clang también puede detectar cuándo std::unique_ptrno se pasa<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.

Actualice a continuación con un enlace al trabajo que agregó esto al sonido, las pruebas y un error que encontré.

Esto se agregó a clang con reviews.llvm.org/D4661 - "Detectar usos" nuevos "y" eliminar "que no coinciden" .

Las pruebas están en prueba / Análisis / MismatchedDeallocator-checker-test.mm

Encontré este error abierto: bugs.llvm.org/show_bug.cgi?id=24819

thatsafunnyname
fuente
Nadie duda de que puede encontrar un analizador estático que detecte un uso incorrecto específico , en cambio, uno que detecte todos los usos incorrectos (y con suerte no
confunda