2012-05-15 22 views
10

Tenemos varias bases de código C de tamaño moderado que reciben confirmaciones de desarrolladores con una variedad de niveles de experiencia. Algunos de los programadores menos disciplinados cometen declaraciones assert() con efectos secundarios que causan errores con aserciones desactivadas. P.ej.Catching assert() con efectos secundarios

assert(function_that_should_always_be_called()); 

Ya utilizamos nuestra propia assert() aplicación, pero la evaluación de la expresión con NDEBUG definido causaría degradaciones de rendimiento inaceptables. ¿Existe una extensión o bandera GCC que podamos aprobar que genere advertencias/errores de tiempo de compilación para estos? Con un flujo de control lo suficientemente simple, GCC debería poder determinar que solo está llamando funciones puras.

+0

No, GCC no comprueba si las funciones tienen efectos secundarios. –

+4

Quizás requiera una revisión de código antes de la confirmación. –

+0

Para aclarar mi pregunta, no contamos con los recursos para realizar una revisión manual del código en algunos de estos repositorios. Para nuestros de alta prioridad, hacemos una revisión del código. Estaba buscando algo automatizado que nos ahorrara tener que rechazar compromisos para estos errores triviales. – Matthew

Respuesta

3

Con un flujo de control lo suficientemente simple, GCC debe poder determinar que solo está llamando funciones puras.

Y si no se trata de un flujo de control lo suficientemente simple, ¿cómo sabrá si es puro o no?


Algo como esto es probablemente su mejor apuesta:

#ifdef NDEBUG 
#define assert(s) do { (s); } while(false) 
#else 
// ... 
#endif 

Varias expresiones serían compilados a cabo, incluyendo funciones con __attribute__((pure)).

La solución más lógica sería simplemente revisar su código y corregir los errores.

+1

De acuerdo: para los usos correctos de 'assert()', donde la expresión no tiene efectos secundarios, el compilador podrá eludir el código siempre que tenga habilitada la optimización. Un lanzamiento a '(void)' también es útil aquí, ya que puede evitar que el compilador advierta sobre una declaración sin efectos secundarios. – caf

+0

Obviamente, un flujo de control más complicado y cosas como la recursión pueden convertir esto en un problema de detención, pero la mayoría de las afirmaciones son relativamente simples. Estaba previendo algún control con un límite de tiempo de espera o límite de profundidad de llamada máximo. En cuanto a la sugerencia, como dije en la pregunta, específicamente, no quiero que se evalúen todas las afirmaciones cuando se define NDEBUG. – Matthew

+0

@Matthew "Estaba previendo algunos controles con un límite de tiempo de espera o límite de profundidad de llamadas máximo". - ¿Qué diablos tiene eso que ver con una verificación en tiempo de compilación para funciones no puras? No tiene ningún sentido en absoluto en su desesperación por una característica de GCC que un momento de reflexión ... o una lectura del manual ... hace obvio que no existe. –

4

Incluso si GCC pudiera detectar cálculos puros (lo cual requeriría resolver el problema de detención), una bandera debería tener poderes mágicos adicionales para notar que un cálculo no puro se aprobó como argumento para su afirmación local macro. Una extensión tampoco podría ayudar, ¿qué se supone que debe hacer exactamente?

La solución a su problema es

  1. contratar a los desarrolladores competentes.
  2. Eduque a sus desarrolladores sobre cómo usar los asertos (entre otras cosas).
  3. Revise el código.
  4. Hacer todas las pruebas contra versiones entregables - si las afirmaciones son off en entregables, entonces assert (function_that_should_always_be_called()) no es diferente a simplemente omitiendo function_that_should_always_be_llame(), que es un error flagrante que debe ser atrapado en la prueba.
+3

Creo que estas "soluciones" no son útiles. Pero, lo que es más importante, dado que los cálculos puros no pueden ser "siempre" detectados, eso no significa que la eliminación de la mayoría de los cálculos puros no sea inmensamente útil. http://stackoverflow.com/a/35294344/6918 –

+0

Lo que no es útil son: a) Citas de susto b) expresar una opinión personal de que estas soluciones no son útiles, sin refutación ofrecida c) descarados hombres de paja - nadie dijo que la detección de cómputos más puros no es útil d) downvotes driveby. La respuesta de Bruno proporciona una técnica inteligente y debe ser aceptada, pero es posible proporcionar tales respuestas sin ser un imbécil. –

5

A pesar de las muchas respuestas no útiles que ha recibido esta pregunta, creo que tiene mucho mérito en el contexto de una base de código heredado.

Imagine que se han acumulado muchas afirmaciones a lo largo de los años, pero como no existía la costumbre de compilar/probar con NDEBUG, algunos efectos secundarios se han filtrado en las afirmaciones y ahora no se atreve a deshabilitar las afirmaciones nunca más.

Puede activar NDEBUG y detectar algunas fallas de prueba en su banco de pruebas, pero no es totalmente sencillo vincular una falla de prueba con la afirmación 'efectiva' porque puede estar muy lejos del punto donde detecta la falla . E incluso un conjunto de pruebas con buena cobertura no se puede confiar en que esté completo.

Puede realizar una revisión del código de todas las aserciones en el código, pero esto es potencialmente mucho trabajo y propenso a errores humanos. Sería mucho mejor si algún análisis estático ya puede eliminar todas las afirmaciones donde pruebe que no aparecen efectos secundarios y solo debe investigar aquellos casos en los que no se garantiza su ausencia.

Así es como puede usar el optimizador de su compilador para llevar a cabo dicho análisis estático. Supongamos que se organizan para sustituir a la definición de la macro assert por:

extern int not_supposed_to_survive; 
#define assert(expr) ((void)(not_supposed_to_survive || (expr))) 

Si expr tiene ningún efecto secundario, la ejecución del efecto está condicionada a que el valor de la variable global not_supposed_to_survive. Pero si expr no tiene ningún efecto secundario, el valor de la variable global no importa (tenga en cuenta que el resultado expr se descarta). Un buen optimizador lo sabe y eliminará la carga de la variable global not_supposed_to_survive, de ahí el nombre de la variable.

Si nuestro programa no contiene una definición del símbolo not_supposed_to_survive, obtendremos un error de enlace cuando la carga no se elimine y podemos usar esto para detectar una afirmación potencialmente efectiva.

E.g. con gcc 4.8:

int g; 

int foo() { return ++g; } 

int main() { 
    assert(foo()); 
    return 0; 
} 

gcc -O2 assert_effect.c 
/tmp/ccunynya.o: In function `main': 
assert_effect.c:(.text.startup+0x2): undefined reference to `not_supposed_to_survive' 
collect2: error: ld returned 1 exit status 

¡El compilador me ayudó a encontrar una afirmación dudosa! Por otro lado, si reemplazo ++g por g+1, el error de enlace desaparece y no tengo que investigarlo. De hecho, esa afirmación está garantizada inofensiva.

Por supuesto, el concepto de "efectos secundarios probables" está limitado por lo que el optimizador "puede ver". Para un análisis más preciso, recomendaría utilizar la optimización de tiempo de enlace (gcc -flto) para analizar en unidades de compilación.

Actualización: Apliqué esto en una base de código C++ de la vida real usando gcc 5.3. Para utilizar la optimización de tiempo de enlace, básicamente utiliza gcc -flto -g como el compilador/enlazador (la opción -g en el compilador/enlazador para obtener una referencia de línea en los errores de enlace) y gcc-ar y gcc-ranlib como archivador/indexador para las bibliotecas estáticas.

Esta configuración podría reducir enormemente el número de afirmaciones que tuve que investigar. Con una mano de obra mínima, pude obtener las afirmaciones limpias.Los falsos positivos que todavía tenía que bajar manualmente se debieron a:

  • función virtual llama
  • no triviales Bucles/recurrencias (donde el optimizador no puede demostrar que son finitos)

además, también me gustaría conseguir algunas afirmaciones que, efectivamente contenían efectos secundarios, pero son inofensivos o no significativos, tales como:

  • funciones de registro que contienen estados
  • Funciones esa caché de su resultado (s)
0

no estoy seguro de si sería suficiente para la aplicación que usted ha descrito, pero cppcheck busca "assertWithSideEffect" s: http://cppcheck.sourceforge.net/devinfo/doxyoutput/checkassert_8cpp_source.html

Aquí está cómo se ve el mensaje en tiempo de compilación: [assertWithSideEffect] myFile.cpp: 42: warning: Función no pura: se llama a 'myFunction' dentro de la declaración assert. Las declaraciones de confirmación se eliminan de las compilaciones de lanzamiento, de modo que el código dentro de la declaración de afirmación no se ejecuta. Si el código es necesario también en versiones de lanzamiento, este es un error.

"Cppcheck es una herramienta de análisis estático para el código C/C++. A diferencia de los compiladores C/C++ y muchas otras herramientas de análisis, no detecta errores de sintaxis en el código. Cppcheck detecta principalmente los tipos de errores que los compiladores normalmente no detectar. El objetivo es detectar solo errores reales en el código (es decir, tener cero falsos positivos) ". http://cppcheck.sourceforge.net/

Cuestiones relacionadas