Red de conocimiento informático - Computadora portátil - ¿Cuál es la razón por la cual la versión de depuración tiene errores pero la versión de lanzamiento se ejecuta normalmente?

¿Cuál es la razón por la cual la versión de depuración tiene errores pero la versión de lanzamiento se ejecuta normalmente?

En primer lugar, la diferencia esencial entre los métodos de depuración y compilación de lanzamiento

La depuración generalmente se denomina versión de depuración, que contiene información de depuración sin ninguna optimización para facilitar a los programadores la depuración de programas. La versión de lanzamiento se denomina versión de lanzamiento y a menudo optimiza el programa para optimizar el tamaño del código y la velocidad de ejecución para que los usuarios puedan usarlo bien.

El verdadero secreto para depurar y publicar reside en un conjunto de opciones de compilación. Las opciones para ambas se enumeran a continuación (por supuesto, hay otras, como /Fd /Fo, pero la diferencia no es importante y normalmente no causa errores de publicación, por lo que no las discutiré aquí).

Versión de depuración:

/MDd /MLd o /MTd utiliza la biblioteca de tiempo de ejecución de depuración (versión de depuración de la biblioteca de funciones de tiempo de ejecución).

/OdDesactivar el interruptor de optimización

/D "_DEBUG" es equivalente a #define _DEBUG. Active el interruptor de compilación y depuración de código (utilizado principalmente para

función de aserción)

/ZI crea una base de datos "Editar y continuar" para la depuración.

Si el código fuente se modifica durante este proceso, no es necesario volver a compilarlo.

/GZ puede ayudar a detectar errores de memoria.

/Gm Active el interruptor de minimizar la vinculación para reducir el tiempo de vinculación.

Versión de lanzamiento:

/MD /ML o /MT utiliza la versión de lanzamiento de la biblioteca de tiempo de ejecución.

El interruptor de optimización /O1 o /O2 hace que el programa sea más pequeño o más rápido.

/D "NDEBUG" desactiva la compilación condicional y el interruptor de código de depuración (es decir, la función de afirmación no se compila).

/GF fusiona cadenas duplicadas y coloca constantes de cadena en la memoria de solo lectura para evitar

que se modifiquen

De hecho, entre la depuración y la versión no hay Hay un límite esencial entre ellos, son solo un conjunto de opciones de compilación y el compilador simplemente actúa de acuerdo con las opciones predeterminadas. De hecho, incluso podemos modificar estas opciones para obtener una compilación de depuración optimizada o una compilación de lanzamiento con declaraciones de seguimiento.

2. ¿En qué circunstancias la versión de lanzamiento saldrá mal?

Con la introducción anterior, comparemos estas opciones una por una para ver cómo ocurre el error de la versión de lanzamiento.

1. Biblioteca de tiempo de ejecución: la biblioteca de funciones de tiempo de ejecución vinculada generalmente solo afecta el rendimiento del programa. Las versiones de depuración de la biblioteca en tiempo de ejecución contienen información de depuración y emplean algunos mecanismos de protección para ayudar a encontrar errores, por lo que el rendimiento no es tan bueno como el de la versión de lanzamiento. La biblioteca de tiempo de ejecución proporcionada por el compilador generalmente es estable y no causará errores en la versión de lanzamiento; por el contrario, la biblioteca de tiempo de ejecución de depuración fortalece la detección de errores, como la asignación de memoria del montón. A veces habrá errores en la depuración pero en la versión normal. . Cabe señalar que si hay un problema con Debug, incluso si Release es normal, debe haber un problema con el programa, pero puede ser que la versión de Release no se muestre en un momento determinado.

2. Optimización: esta es la razón principal de los errores, porque cuando la optimización está desactivada, el programa fuente básicamente se traduce directamente. Después de activar la optimización, el compilador hará una serie de suposiciones. Este tipo de error incluye principalmente los siguientes tipos:

(1) Elipsis del puntero de marco (FPO para abreviar): durante el proceso de llamada a la función, toda la información de la llamada (dirección del remitente, parámetros) y las variables automáticas se colocan la pila. Si la declaración de la función difiere de la implementación (parámetros, valor de retorno, método de llamada), se producirá un error, pero en el modo de depuración, el acceso a la pila se realiza a través de la dirección guardada en el registro EBP, y si no hay una matriz intersección (o cruces "no muchos"), la función generalmente se puede ejecutar normalmente en el modo de lanzamiento, la optimización omitirá el puntero base de la pila EBP, por lo que acceder a la pila a través del puntero global provocará un error de dirección de retorno y un bloqueo del programa; . La naturaleza fuertemente tipada de C puede detectar la mayoría de los errores, pero no si utiliza conversiones.

Puede forzar la opción /Oy-compile para desactivar las elipses del puntero del marco en las versiones de lanzamiento para determinar si se produce este error. Estos errores suelen incluir:

● Error de escritura de la función de respuesta de mensaje MFC. La declaración correcta debe ser

afx _ msg LRESULT OnMessageOwn(WPARAM WPARAM, LPARAM LPARAM);

La macro ON_MESSAGE contiene conversión forzada. Una forma de evitar este error es redefinir la macro ON_MESSAGE y agregar el siguiente código a stdafx.h (después de #include "afx win.h"). Cuando el prototipo de la función es incorrecto, la compilación informará un error.

#undef abrir mensaje

#define ON_MESSAGE(message, memberFxn) { mensaje, 0, 0, 0, AfxSig_lwl, (AFX _ PMSG) (AFX _ PMS GW) (estático _ cast lt;LRESULT(AFX_MSG_CALL CWnd::*)(WPARAM,LPARAM) gt;(ampmemberFxn)},

(2) variable volátil: volátil le dice al compilador que la variable puede ser Para mejorar Para el rendimiento del programa, el optimizador a menudo coloca algunas variables en registros (similar a la palabra clave de registro), mientras que otros procesos solo pueden modificar la ubicación de las variables. Los valores en la memoria y los registros permanecen sin cambios. -threaded, o descubre que el valor de una variable no es el que esperaba y está seguro de que se ha configurado correctamente, es probable que encuentre un problema de este tipo. Este error a veces indica que el programa está mal en el archivo . optimización más rápida y es normal en la optimización más pequeña.

(3) Optimización de variables: el optimizador optimizará las variables en función de su uso. Por ejemplo, si hay una variable no utilizada en una función, se eliminará una matriz. Los límites pueden estar enmascarados en la versión de depuración, pero en la versión de lanzamiento, es probable que la variable esté optimizada y la matriz fuera de los límites Destruirá los datos útiles en la pila. Por supuesto, la situación real será mucho más complicada que esto. Los errores relacionados con esto incluyen:

●Acceso ilegal, incluidos matrices fuera de límites, errores de puntero, etc.

Fn no válido (no válido)

. {

int I;

I = 1;

int a[4]; int j;

j = 1;

}

a[-1]= 1; // Por supuesto, el error no es tan obvio, por ejemplo. , el subíndice es una variable

a[4]= 1;

}

Aunque cuando la matriz está fuera de los límites, J no está dentro del rango. pero su espacio no ha sido restaurado, por lo que I y J cubrirán los límites. Sin embargo, la versión de lanzamiento puede optimizarse porque I y J tienen poco efecto, lo que destruirá la pila. >3._DEBUG. Y NDEBUG: cuando se define _DEBUG, la función afirmar () se compilará, pero NDEBUG no se compilará. Además, hay una serie de macros de aserción en VC.

Esto incluye:

ANSI C afirma void afirmar (expresión int);

c runtime Lib afirma _ ASSERT (expresión booleana);

_ ASSERTE (expresión booleana) );

MFC afirma ASSERT(expresión booleana);

VERIFY(expresión booleana);

ASSERT _ VALID(po objeto);

ASSERT_KINDOF(nombre de clase, objeto po);

ATL afirma ATLASSERT(expresión booleana);

Además, la compilación de la macro TRACE() también está controlada por _DEBUG.

Todas estas afirmaciones se compilan solo en compilaciones de depuración y se ignoran en compilaciones de lanzamiento. La única excepción es VERIFICAR(). De hecho, todas estas macros llaman a la función afirmar (), pero se adjunta algún código de depuración relacionado con la biblioteca. Si agrega cualquier código de programa a estas macros que no sean solo expresiones booleanas (como asignaciones, llamadas a funciones que cambian el valor de una variable, etc.), la versión de lanzamiento no realizará estas operaciones, lo que generará un error. Los novatos tienden a cometer este error, el método de búsqueda es simple porque estas macros se enumeran arriba. Simplemente use la función Buscar en archivos de VC para encontrar los lugares donde se usan estas macros en todos los archivos del proyecto y luego verifíquelos uno por uno. Además, algunos expertos también pueden agregar compilación condicional como #ifdef _DEBUG, así que tenga cuidado.

Por cierto, vale la pena mencionar la macro VERIFY(), que permite poner el código del programa en una expresión booleana. Esta macro se utiliza normalmente para comprobar los valores de retorno de las API de Windows. Algunas personas pueden abusar de VERIFY() por este motivo. De hecho, esto es muy peligroso, porque VERIFY () viola la idea de aserción y no puede separar completamente el código del programa y el código de depuración, lo que eventualmente puede causar muchos problemas. Por ello, los expertos recomiendan utilizar esta macro lo menos posible.

Opción 4./GZ: Esta opción realizará las siguientes operaciones.

(1) Inicializar memoria y variables. Incluyendo inicializar todas las variables automáticas con 0xCC, inicializar la memoria asignada en el montón con 0xCD (datos borrados) (es decir, memoria asignada dinámicamente, como nueva), llenar la memoria del montón liberada con 0xDD (datos muertos, como eliminar), y 0x FD (defencedata) ) Inicializa la memoria protegida (la versión de depuración agrega memoria protegida antes y después de la asignación de memoria dinámica para evitar el acceso fuera de límites), donde las palabras entre paréntesis son mnemotécnicos recomendados por Microsoft. La ventaja de esto es que estos valores son muy grandes, por lo que es imposible usarlos como punteros (y los punteros en sistemas de 32 bits rara vez tienen valores impares, y los punteros de valores impares pueden causar errores de tiempo de ejecución en algunos sistemas). se utilizan como valores que rara vez se encuentran y estos valores son fáciles de identificar, por lo que es muy beneficioso encontrar solo errores que se encontrarían en las compilaciones de depuración en las compilaciones de lanzamiento. Cabe señalar que muchas personas piensan que el compilador inicializará las variables con 0, lo cual es incorrecto (y tampoco conduce a encontrar errores).

(2) Al llamar a una función a través de un puntero de función, se verificará el puntero de la pila para verificar la coincidencia de la llamada de función. (Para evitar que el prototipo no coincida)

(3) Verifique el puntero de la pila antes de que la función regrese para confirmar que no ha sido modificado. (Para evitar el acceso fuera de límites y la discrepancia en el prototipo, combinado con el segundo elemento, el puntero del marco se puede simular de forma aproximada y se puede omitir el FPO).

Normalmente, la opción /GZ provocará una error en la versión de depuración y la versión de lanzamiento será normal, porque la versión de lanzamiento Las variables no inicializadas en la versión son aleatorias y pueden hacer que el puntero apunte a una dirección válida, enmascarando el acceso ilegal.

Además, opciones como /Gm /GF rara vez causan errores, sus efectos son obvios y fáciles de encontrar.

En tercer lugar, cómo "depurar" el programa de publicación

Obviamente, es muy frustrante encontrar un éxito en la depuración pero un fracaso en la publicación y, a menudo, no hay forma de comenzar. Si lee el análisis anterior y combina las manifestaciones específicas del error, podrá encontrar el error rápidamente. Pero si no lo encuentras en este momento, existen algunas estrategias para esta situación.

1. Como se mencionó anteriormente, Depurar y Liberar son solo la diferencia entre un conjunto de opciones de compilación, y en realidad no existe una definición para distinguirlas. Podemos modificar las opciones de compilación de la versión de lanzamiento para limitar el alcance del error. Como se mencionó anteriormente, puede cambiar las opciones de versión una por una a las opciones de depuración correspondientes, como /MD a /MDd, /O1 a /Od, o optimización del tiempo de ejecución a optimización del tamaño del programa. NOTA: Cambie solo una opción a la vez y vea en qué opción desaparece el error cuando la cambie, luego busque errores relacionados con esa opción. Estas opciones se pueden seleccionar directamente desde la lista en Proyecto\Configuración... y normalmente no requieren modificación manual. Debido a que el análisis anterior es relativamente completo, este método es el más eficaz.

2. Durante el proceso de programación, siempre preste atención a probar la versión publicada para evitar demasiado código y poco tiempo.

3. Utilice el nivel de advertencia /W4 en compilaciones de depuración para obtener la máxima información de error del compilador. Por ejemplo, si (i =0) generará una advertencia /W4. No ignore estas advertencias, generalmente son causadas por errores en el programa. Pero a veces /W4 traerá mucha información redundante, como advertencias sobre parámetros de funciones no utilizados. Muchas funciones de procesamiento de mensajes ignorarán algunos parámetros. Podemos usar

#progma advertencia (deshabilitado: 4702) // deshabilitar

// ...

#progma advertencia (predeterminado: 4702) // Permitir de nuevo

Desactive temporalmente la advertencia o use

#progma advertencia(push, 3) //Establezca el nivel de advertencia en /W3.

// ...

#progma advertencia(pop) //Restablecer a /W4

Para cambiar temporalmente el nivel de advertencia, a veces puedes simplemente usar /W4 para partes del código consideradas sospechosas.

4. También puedes depurar tu versión de lanzamiento como Debug, solo agrega símbolos de depuración. En Proyecto/Configuración..., seleccione la configuración para "Versión Win32", seleccione la pestaña C/C, seleccione "General" como categoría, seleccione "Base de datos del programa" como información de depuración. Agregue "/OPT:REF" al final de las opciones del proyecto de etiqueta de enlace (no pierda las comillas). Esto permite que el depurador utilice símbolos de depuración en el archivo pdb. Pero al depurar, encontrará que los puntos de interrupción son difíciles de establecer y las variables son difíciles de encontrar; todos están optimizados. Afortunadamente, sin embargo, la ventana de la pila de llamadas todavía funciona bien. Incluso si el puntero del marco está optimizado, aún se puede encontrar información de la pila (especialmente la dirección del remitente). Esto es útil para errores de posicionamiento.