2010-06-22 17 views
11

He visto publicaciones sobre que causan diferencias entre las compilaciones Debug y Release, pero no creo que nadie haya abordado desde el punto de vista del desarrollo cuál es la forma más eficiente de resolver el problema.¿Mejores prácticas y herramientas para eliminar las diferencias entre compilaciones de Debug y Release?

Lo primero que hago cuando aparece un error en la compilación Release pero no en Debug es ejecutar mi programa a través de valgrind con la esperanza de un mejor análisis. Si eso no revela nada, - y esto me ha pasado antes - entonces pruebo varias entradas con la esperanza de hacer que el error surja también en la compilación Debug. Si eso falla, entonces trataría de rastrear los cambios para encontrar la versión más reciente para la cual las dos compilaciones difieren en comportamiento. Y finalmente, supongo que recurriría a las declaraciones impresas.

¿Existen mejores prácticas de ingeniería de software para la depuración eficiente cuando difieren las versiones de depuración y liberación? Además, ¿qué herramientas hay que operen en un nivel más fundamental que valgrind para ayudar a depurar estos casos?

EDIT: Observé muchas respuestas que sugieren algunas buenas prácticas generales, como las pruebas unitarias y las pruebas de regresión, que acepto son excelentes para encontrar cualquier error. Sin embargo, ¿hay algo específicamente adaptado a este problema de liberación frente a depuración? Por ejemplo, ¿hay algo así como una herramienta de análisis estático que dice "Oye, esta macro o este código o esta práctica de programación es peligrosa porque tiene el potencial de causar diferencias entre tus compilaciones de depuración/versión?"

+1

Con VC, puede instrumentar las compilaciones de lanzamiento con información de depuración y depurarlas también. No hay mucha diversión en recorrer el código optimizado, pero tienes una buena posibilidad de encontrar errores de esa manera. – sbi

+0

Correcto, se me olvida que también puede compilar información de depuración en la versión de lanzamiento con gcc. – Eric

Respuesta

3

La existencia de dos configuraciones es un problema desde el punto de vista de la depuración. La ingeniería adecuada sería tal que el sistema en el suelo y en el aire se comporte de la misma manera, y logre esto reduciendo la cantidad de formas en que el sistema puede notar la diferencia.

depuración y las versiones de lanzamiento difieren en 3 aspectos:

  • _DEBUG definen
  • optimizaciones
  • versión diferente de la biblioteca estándar

La mejor manera de alrededor, el camino a menudo trabajo , es esto:

  • Deshabilitar optimizaciones donde el rendimiento no es crítico. La depuración es más importante. Lo más importante es desactivar la función de autoinflado, mantener el marco de pila estándar y las optimizaciones de reutilización variable. Estas molestias depuran la mayoría.
  • Código de monitor para la dependencia de definir DEPURAR. Nunca utilice afirmaciones de solo depuración, ni ninguna otra herramienta sensible a la definición de DEPURACIÓN.
  • Por defecto, compilar y trabajar/liberar.
+1

Creo que esto llega al corazón del problema. Cuando lo piensas, las técnicas como las pruebas unitarias y las pruebas de regresión intentan localizar errores bisectando el tiempo y el espacio que ha transcurrido entre una versión funcional y una versión rota. – Eric

+0

Pero la depuración de la diferencia entre una versión y una creación de depuración debería ocurrir al dividir las diferencias entre una depuración y una compilación de lanzamiento porque no necesita pruebas de unidad si solo desea que el error aparezca en su depurador. Como mencionó, estos incluyen optimizaciones y diferencias en la dependencia. – Eric

0

Cuando depuración y liberación difieren Esto significa:

  1. que el código depende de la _DEBUG o macros similares (definida al compilar una versión de depuración - no optimizar)
  2. su compilador tiene un error de optimización (he visto esto pocas veces)

Puede tratar fácilmente con (1) (modificación del código) pero con (2) deberá aislar el error del compilador. Después de aislar el error, se hace un poco de "reescritura de código" para que el compilador genere el código binario correcto (lo hice varias veces; la parte más difícil es aislar el error).

Puedo decir que cuando se habilita la información de depuración para la versión de lanzamiento el proceso de depuración funciona ... (aunque debido a las optimizaciones es posible que vea algunos saltos "extraños" cuando se ejecuta).

Necesitará tener algunas pruebas de "caja negra" para su aplicación - valgrind es una solución en este caso. Estas soluciones lo ayudan a encontrar diferencias entre la versión y la depuración (que es muy importante).

+1

Por lo que he visto, la mayoría de los errores que surgen con la activación de las optimizaciones no son errores en el optimizador, sino en el código que se está compilando. Las optimizaciones solo los hacen aparecer. Cosas como la falta de "volátil" y "restringir" pueden causar este tipo de problema. Los compiladores respetables modernos tienen errores, pero también pasan por MUCHAS pruebas automatizadas. – nategoose

1

La causa más obvia es simplemente el uso de #ifdef y #ifndef directivas asociadas DEBUG o símbolo similar que cambian entre las dos construcciones.

Antes de ir por la ruta de depuración (que no es mi idea personal de diversión), inspeccionaba ambas líneas de comando y comprobaba qué banderas pasaban en un modo y no en el otro, luego grep mi código para estas banderas y marcas sus usos.

Una cuestión particular que viene a la mente son las macros:

#ifdef _DEBUG_ 
    #define CHECK(CheckSymbol) { if (!(CheckSymbol)) throw CheckException(); } 
#else 
    #define CHECK(CheckSymbol) 
#endif 

también conocido como un soft-aserción.

Bueno, si lo llamas con una función que tiene un efecto secundario, o confías en él para proteger una función (cumplimiento del contrato) y de alguna manera atrapa la excepción que arroja depuración e ignóralo ... verás diferencias en lanzamiento :)

3

Cuando me encuentro con un error que solo ocurre en el lanzamiento, lo primero que siempre busco es el uso de una variable de pila no inicializada en el código en el que estoy trabajando. En Windows, el tiempo de ejecución de depuración C inicializará automáticamente las variables de la pila a un patrón de bits conocido, 0xcdcdcdcd o algo así. En la versión, las variables de pila contendrán el valor que se almacenó por última vez en esa ubicación de memoria, que será un valor inesperado.

En segundo lugar, intentaré identificar qué es diferente entre las compilaciones de depuración y lanzamiento. Miro la configuración de optimización del compilador que el compilador pasa en las configuraciones Debug y Release. Puede ver que esta es la última página de propiedades de la configuración del compilador en Visual Studio. Comenzaré con la configuración de lanzamiento y cambiaré los argumentos de línea de comando pasados ​​al compilador de un elemento a la vez hasta que coincidan con la línea de comando que se usa para compilar en depuración. Después de cada cambio, ejecuto el programa y reproduzco el error. Esto a menudo me llevará a la configuración particular que hace que el error suceda.

Una tercera técnica puede ser tomar una función que se comporta mal y deshabilitar las optimizaciones a su alrededor con el pre-procesador. Esto le permitirá ejecutar el programa en versión con la función particular compilada en depuración. El comportamiento del programa que se ha creado de esta manera lo ayudará a aprender más sobre el error.

#pragma optimize("", off) 
void foo() { 
    return 1; 
} 
#pragma optimize("", on) 

Por experiencia, los problemas son por lo general pila de inicialización, la memoria de lavado en el asignador de memoria, o extrañas #define directivas que causan el código para ser compilado incorrectamente.

+2

Otro problema común que corro es el código que tiene un efecto secundario en una macro assert(). La macro se compila en el lanzamiento, y el efecto secundario nunca ocurre. –

4

Otra "Mejor práctica", o más bien una combinación de dos: tener pruebas unitarias automatizadas, y dividir y conquistar.

Si tiene una aplicación modular y cada módulo tiene buenas pruebas de unidad, entonces puede aislar rápidamente la pieza errante.

+0

+1 La unidad prueba lpp de dos maneras: (1) No tiene que hacer mucho (depuración). (2) La depuración del código de prueba de la unidad a menudo es un atajo al origen del error. –

0

La mejor solución es configurar algo así como pruebas unitarias automatizadas para probar exhaustivamente todos los aspectos de la aplicación (no solo componentes individuales, sino pruebas del mundo real que utilizan la aplicación de la misma manera que un usuario común con todas las dependencias) Esto le permite saber de inmediato cuando se ha introducido un error de liberación que le dará una buena idea de dónde está el problema.

Una buena práctica para monitorear activamente y buscar problemas supera a cualquier herramienta que te ayude a solucionarlos mucho después de que ocurran.

Sin embargo, cuando tienes uno de esos casos en los que es demasiado tarde: han pasado demasiadas construcciones, no se pueden reproducir de manera consistente, etc., entonces no conozco ninguna herramienta para el trabajo. A veces, jugar con la configuración de lanzamiento puede dar una idea de por qué está ocurriendo el error: si puede eliminar las optimizaciones que de repente hacen que el error desaparezca, eso podría proporcionarle información útil al respecto.

Release-bichos sólo pueden caer en varias categorías, pero las más comunes (aparte de algo así como un mal uso de afirmaciones) es:

1) la memoria sin inicializar. Utilizo este término sobre las variables no inicializadas ya que una variable puede inicializarse pero aún estar apuntando a la memoria que no se ha inicializado correctamente. Para esto, las herramientas de diagnóstico de memoria como Valgrind pueden ayudar.

2) Temporización (por ejemplo, condiciones de carrera). Estos pueden ser una pesadilla para depurar, pero hay algunos perfiladores multihilo y herramientas de diagnóstico que pueden ayudar. No puedo sugerir ninguna de inmediato, pero Coverity Integrity Manager es un ejemplo.

Cuestiones relacionadas