¿Por qué la versión DEBUG es correcta pero la versión Release es incorrecta?
La depuración generalmente se denomina versión de depuración. Contiene información de depuración y no está optimizada para que los programadores depuren el programa. La versión a menudo se denomina versión de lanzamiento, que realiza varias optimizaciones para optimizar el programa en términos de tamaño y velocidad del código para que los usuarios puedan utilizar bien el programa.
El verdadero secreto para depurar y publicar compilaciones es el conjunto de opciones de compilación. La siguiente es una lista de opciones para ambas versiones (hay algunas otras opciones, como /Fd /Fo, pero sus diferencias no son importantes y generalmente no causan errores en la versión de lanzamiento, por lo que no se analizan aquí) p>
Compilación de depuración:
/MDd /MLd o /MTd usa la biblioteca de tiempo de ejecución de depuración.
/Od desactiva el interruptor de optimización
/D"_DEBUG "es equivalente a #define _DEBUG, que activa el interruptor de compilación y depuración de código (utilizado principalmente para
función de afirmación)
/ZI crea, edita y continúa la base de datos.
/ZI crea una base de datos de edición y continuación para que no tengas que volver a compilar cuando cambies el código fuente durante la depuración
/GZ ayuda a detectar errores de memoria
/ Gm Active el interruptor para minimizar la vinculación para minimizar el tiempo de vinculación
Versión de lanzamiento:
/MD /ML o /MT Utilice la versión de lanzamiento de la biblioteca de tiempo de ejecución
/O1 o /O2 Optimizar para versiones de lanzamiento
/MD /ML o /MT. > /O1 o /O2 modificador de optimización para hacer que el programa sea el más pequeño o más rápido
/D "NDEBUG" desactiva la compilación condicional y el modificador de código de depuración (es decir, no compila la función de afirmación)
/GF Fusiona cadenas duplicadas y coloca constantes de cadena en la memoria de solo lectura para evitar
modificaciones
De hecho, no existe un límite intrínseco entre Depurar y Liberar, solo una; colección de opciones de compilación, y el compilador simplemente actúa en función de las opciones predefinidas. De hecho, incluso puede modificar estas opciones para obtener una compilación de depuración optimizada o una compilación de lanzamiento con declaraciones de seguimiento.
En segundo lugar, qué errores pueden ocurrir en la versión Release
Teniendo esto en cuenta, veamos estas opciones una por una para ver cómo ocurren los errores en la versión Release
1. Biblioteca de tiempo de ejecución: la biblioteca de tiempo de ejecución a la que se vincula generalmente solo tiene un impacto en el rendimiento de su 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 su 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, debido a que la biblioteca de tiempo de ejecución de depuración tiene una detección mejorada de errores como la asignación de memoria dinámica, a veces pueden ocurrir errores en la versión de depuración; La versión de lanzamiento es normal. Cabe señalar que si ocurre un error de depuración, incluso si la versión de lanzamiento es normal, debe haber un error en el programa, pero es posible que no se muestre durante la ejecución de la versión de lanzamiento.
2. Optimización: esta es la principal causa de errores, porque cuando la optimización está desactivada, el programa fuente básicamente se traduce directamente, pero cuando la optimización está activada, el compilador hará una serie de suposiciones. Los principales tipos de errores de este tipo son los siguientes:
(1) Omisión del puntero de trama (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 en 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 generará un error----, pero en el modo de depuración, el acceso a la pila se logra a través de la dirección almacenada en el registro EBP. Si no hay errores como matriz fuera de límites (en el modo de lanzamiento, la optimización omite 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 el programa fallará. Las sólidas capacidades de escritura de C Puede verificar la mayoría de estos errores, pero si usa Casting no lo hace. Puede forzar la opción /Oy-compil para desactivar las omisiones del puntero del marco en las compilaciones de lanzamiento para determinar si dichos errores existen:
● MFC. Respuesta del mensaje. Escritura de función incorrecta. La función correcta debe ser
afx_msg LRESULT OnMessageOwn(WPARAM wparam, LPARAM lparam);
Una forma de evitar este error es incluir una conversión. la macro ON_MESSAGE, en stdafx.h (después de #include "afxwin.h "), si la función genera un error, se informará un error de compilación
#undef ON_MESSAGE
# define ON_MESSAGE(mensaje, memberFxn) { mensaje, 0, 0, 0, AfxSig_lwl, (AFX_PMSG)(AFX_PMSGW)(static_castlt; LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM) gt; (amp; memberFxn) }, < / p>
(2) Variables de tipo volátil: volátil le dice al compilador que la variable puede modificarse de formas desconocidas fuera del programa (por ejemplo, en AFX_PMSG(AFX_PMSGW)(static_castlt; LRESULT (AFX_MSG_CALL CWnd: :*) (WPARAM, LPARAM) g., el sistema, otros procesos y subprocesos). Los optimizadores a menudo colocan variables en registros (similar a la palabra clave de registro) para hacer que los programas tengan más rendimiento y otros procesos solo pueden modificar la memoria donde se encuentran las variables. mientras que el valor en el registro permanece sin cambios. Si su programa tiene múltiples subprocesos o descubre que el valor de una variable es diferente de lo que esperaba y está seguro de que su configuración es correcta, es probable que encuentre esto. Este error también ocurre a veces cuando el programa falla en la optimización más rápida pero funciona bien en la optimización mínima. Intente agregar volátil a la variable que cree que es sospechosa.
(3) Optimización de variables: el optimizador optimizará las variables según su uso. Por ejemplo, si hay una variable no utilizada en una función, en una compilación de depuración podría ocultar una intersección de matriz, mientras que en una compilación de lanzamiento es probable que la variable se optimice porque la intersección de matriz destruye datos útiles en la pila. Por supuesto, la situación real es más complicada que esto. Algunos errores relacionados con esto incluyen:
● Acceso ilegal, incluidos matrices fuera de límites, errores de puntero, etc.
Por ejemplo
void fn(void)
{
int i
i = 1; int a[4];
{
int j
j = 1; a [-1] = 1; // Por supuesto que no habrá error
a[4] = 1
}
Cuando un out- Un evento fuera de rango ocurre en la matriz, j ha salido del alcance, pero su espacio no ha sido recuperado, por lo que i y j enmascaran eventos fuera de rango. Dado que i y j no desempeñan un papel importante, la versión de lanzamiento puede optimizarse, corrompiendo la pila.
3. _DEBUG y NDEBUG: Cuando se define _DEBUG, se compilará la función afirmar(), pero no cuando se define NDEBUG. Además, hay muchas macros de aserción en VC. Estas macros incluyen
ANSI C afirmar void afirmar(int expresión);
C Runtime Lib afirmar _ASSERT( booleanExpression
_ASSERTE( booleanExpression ); p>
p>
Aserción MFC ASSERT( booleanExpression );
VERIFY( booleanExpression
ASSERT_VALID( pObject ); nombre de clase, pobject);
ATL afirmar ATLASSERT( booleanExpression );
Además, la compilación de la macro TRACE() está controlada por _DEBUG.
Todas estas afirmaciones solo se compilan en compilaciones de depuración y se ignorarán en compilaciones de lanzamiento. La única excepción es VERIFICAR(). De hecho, todas estas macros añaden algún código de depuración relacionado con la biblioteca cuando llaman a la función afirmar(). 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 principiantes pueden cometer este error fácilmente, y el método de búsqueda también es muy simple, porque estas macros se enumeran arriba. Simplemente use la función "Buscar en archivos" de VC para buscar y usar estas macros en todos los archivos del proyecto. y luego revísalos uno por uno. Además, algunos expertos pueden agregar #ifdef _DEBUG y otras compilaciones condicionales, así que tenga cuidado.
Por cierto, existe la macro VERIFY(), que le 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 () debido a esto, lo cual en realidad es muy peligroso, porque VERIFY () viola el concepto 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 con precaución.
4.
4. Opción /GZ: Esta opción tiene las siguientes funciones
(1) Inicializar memoria y variables.
Esto incluye inicializar todas las variables automáticas con 0xCC (datos borrados), inicializar la memoria asignada en el montón (es decir, memoria asignada dinámicamente, como nueva) y llenar la memoria del montón liberada (es decir, memoria asignada dinámicamente) con 0xDD (datos muertos). Agregue memoria protegida antes y después de la memoria 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 los valores son lo suficientemente grandes como para que no puedan ser punteros (en sistemas de 32 bits, los punteros rara vez tienen valores impares y, en algunos sistemas, los punteros impares generarán errores de tiempo de ejecución) y los valores numéricos rara vez son encontrado y es fácil de identificar, por lo que es excelente para detectar errores en las compilaciones de depuración que solo se encuentran en las compilaciones de lanzamiento. En particular, mucha gente cree que el compilador inicializa las variables a 0, lo cual es incorrecto (lo cual es muy perjudicial para 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 si la llamada a la función coincide. (Evite discrepancias primitivas).
(3) Verifique el puntero de la pila antes de que la función regrese para confirmar que no ha sido modificado. (Evita el acceso fuera de límites y las discrepancias primitivas, y junto con la segunda opción simula aproximadamente el puntero del marco omitiendo el FPO)
Normalmente, la opción /GZ provocará un error en la versión de depuración y la versión La versión es normal, porque las variables no inicializadas en las versiones de lanzamiento son aleatorias, lo que potencialmente enmascara accesos ilegales al hacer que los punteros apunten a direcciones válidas.
De lo contrario, opciones como /Gm /GF causan menos errores y sus efectos son obvios y más fáciles de encontrar.
3. Cómo "depurar" la versión de lanzamiento del programa
La depuración es exitosa pero la versión falla, lo que obviamente es algo muy frustrante y, a menudo, no hay forma de comenzar. . Si puede encontrar el error rápidamente después de leer el análisis anterior y combinarlo con las manifestaciones específicas del error, será más fácil de manejar. Pero si no puede encontrarlo, aquí tiene algunas estrategias para ello.
1. Como se mencionó anteriormente, Debug y Release son solo un conjunto de opciones de compilación y sus definiciones en realidad no se distinguen. Podemos modificar las opciones de compilación de la versión para limitar el alcance del error. Como se mencionó anteriormente, puede cambiar las opciones de lanzamiento una por una a las opciones de depuración correspondientes, como cambiar /MD a /MDd, /O1 a /Od o cambiar la optimización del tiempo de ejecución a la optimización del tamaño del programa. Tenga en cuenta que solo puede cambiar una opción a la vez, luego buscar errores que desaparecen cuando cambia qué opción y luego buscar errores relacionados con esa opción. Estas opciones se pueden seleccionar directamente desde la lista en Proyecto\Configuración.... Estas opciones se pueden seleccionar directamente desde la lista en Proyecto\Configuración... y generalmente no desea cambiarlas manualmente. Debido a que el análisis anterior es bastante completo, este método es el más eficaz.
2. Asegúrese de probar la versión de lanzamiento durante el proceso de programación para evitar demasiado código y muy poco tiempo.
3. Utilice el nivel de advertencia /W4 en las compilaciones de depuración para obtener la mayor información de error del compilador; por ejemplo, si (i = 0) generará una advertencia /W4. No ignores estas advertencias, normalmente son causadas por errores en el programa. Pero a veces /W4 trae mucha información redundante, como advertencias de parámetros de funciones no utilizadas, y muchos controladores de mensajes ignoran ciertos parámetros.
Podemos usar
#progma advertencia(disable: 4702) // para deshabilitar
//...
#progma advertencia(default: 4702) / /re-allow
Desactiva temporalmente la advertencia o usa
#progma advertencia(push, 3) // Establece el nivel de advertencia en /W3
/ /. código que cree que es sospechoso.
4. También puedes depurar la versión como si fuera la depuración, solo agrega símbolos de depuración. En Proyecto/Configuración... En Proyecto/Configuración..., seleccione la configuración de "Versión Win32", seleccione la pestaña C/C, seleccione General en Categoría, seleccione Base de datos del programa en Información de depuración y luego seleccione Agregar "/OPT :REF" (sin comillas) hasta el final de la pestaña Enlace. El depurador utilizará los símbolos de depuración en el archivo pdb. Pero al depurar, le resultará difícil establecer puntos de interrupción y encontrar variables, que estén optimizadas. Afortunadamente, la ventana Pila de llamadas todavía funciona bien y, aunque el puntero del marco se ha optimizado, todavía se puede encontrar la información de la pila (especialmente la dirección del remitente). Esto ayudará a localizar el error.