Aserciones de C/C++

Una instrucción de aserción especifica una condición que se espera que sea cierta (valor true) en un punto del programa. Si esa condición no es "true", la aserción produce un error, la ejecución del programa se interrumpe y aparece el cuadro de diálogo Error de aserción.

Visual Studio admite instrucciones de aserción de C++ que se basan en estos constructores:

  • Aserciones de MFC para programas.

  • ATLASSERT para programas que utilizan ATL.

  • Aserciones de CRT para programas que utilizan la biblioteca en tiempo de ejecución de C.

  • Función assert de ANSI para otros programas en C o C++.

    Se pueden utilizar aserciones para detectar errores lógicos, comprobar resultados de una operación y probar condiciones de error que deben haberse controlado.

En este tema

Cómo funcionan las aserciones

Aserciones en compilaciones de depuración y de versión

Efectos secundarios del uso de aserciones

Aserciones de CRT

Aserciones de MFC

Cómo funcionan las aserciones

Cuando el depurador se detiene debido a una aserción de MFC o de la biblioteca en tiempo de ejecución de C, si el código fuente está disponible, navega hasta el punto del archivo de código fuente donde ocurrió la aserción. El mensaje de aserción aparece tanto en la Ventana de salida como en el cuadro de diálogo Error de aserción. El mensaje de aserción se puede copiar desde la Ventana de salida en una ventana de texto si se desea guardarlo para consultas posteriores. La Ventana de salida puede contener también otros mensajes de error. Examine estos mensajes con cuidado, ya que pueden proporcionar pistas para encontrar la causa del error de aserción.

Use aserciones para detectar errores durante el desarrollo. En general, utilice una aserción para cada suposición. Por ejemplo, si supone que un argumento no es NULL, utilice una aserción para comprobar esa suposición.

En este tema

Aserciones en compilaciones de depuración y de versión

Las instrucciones de aserción solo se compilan si se define _DEBUG. De lo contrario, el compilador trata las aserciones como instrucciones NULL. Por tanto, las instrucciones de aserción no imponen ninguna sobrecarga ni costo de rendimiento sobre la versión final del programa y permiten evitar utilizar el uso de directivas de #ifdef.

Efectos secundarios del uso de aserciones

Cuando agregue aserciones al código, asegúrese de que no producen efectos secundarios. Por ejemplo, considere la siguiente aserción que modifica el valor nM:

ASSERT(nM++ > 0); // Don't do this!

Como la expresión ASSERT no se evalúa en la versión de lanzamiento del programa, nM tendrá valores diferentes en las versiones de depuración (Debug) y de versión (Release). Para evitar este problema en MFC, puede usar la macro VERIFY en lugar de ASSERT. VERIFY evalúa la expresión en todas las versiones pero no comprueba el resultado en la versión de lanzamiento.

Tenga especial cuidado al utilizar llamadas a funciones en las instrucciones de aserción, ya que la evaluación de una función puede producir efectos laterales inesperados.

ASSERT ( myFnctn(0)==1 ) // unsafe if myFnctn has side effects
VERIFY ( myFnctn(0)==1 ) // safe

VERIFY llama a myFnctn en ambas versiones, de depuración y lanzamiento, por lo que su uso es aceptable. No obstante, el uso de VERIFY impone la sobrecarga de una llamada de función innecesaria en la versión de lanzamiento.

En este tema

Aserciones de CRT

El archivo de encabezado CRTDBG.H define las macros _ASSERT y _ASSERTE para la comprobación de aserciones.

Macro Resultado
_ASSERT Si la expresión especificada se evalúa como FALSE, el resultado es el nombre de archivo y el número de línea de la expresión sometida a _ASSERT.
_ASSERTE Igual que _ASSERT, más una representación de cadena de la expresión sometida a aserción.

_ASSERTE es más eficaz, ya que informa de la expresión sometida a aserción que resultó ser falsa (valor FALSE). Esto puede ser suficiente para identificar el problema sin consultar el código fuente. No obstante, la versión de depuración de la aplicación contiene una constante de tipo cadena para cada expresión sometida a aserción mediante _ASSERTE. Si se utilizan muchas macros _ASSERTE, estas expresiones de cadena ocupan una cantidad de memoria significativa. Si eso constituye un problema, utilice _ASSERT para ahorrar memoria.

Cuando se define _DEBUG, la macro _ASSERTE se define del siguiente modo:

#define _ASSERTE(expr) \
    do { \
        if (!(expr) && (1 == _CrtDbgReport( \
            _CRT_ASSERT, __FILE__, __LINE__, #expr))) \
            _CrtDbgBreak(); \
    } while (0)

Si la expresión sometida a aserción se evalúa como FALSE, se realiza una llamada a _CrtDbgReport para informar del error de aserción (mediante un cuadro de diálogo de mensaje, de forma predeterminada). Si elige Reintentar en el cuadro de diálogo del mensaje, _CrtDbgReport devuelve 1 y _CrtDbgBreak llama al depurador mediante DebugBreak.

Si necesita deshabilitar temporalmente todas las aserciones, use _CtrSetReportMode.

Comprobar si el montón está dañado

El siguiente ejemplo usa _CrtCheckMemory para comprobar si el montón ha sufrido algún daño:

_ASSERTE(_CrtCheckMemory());

Comprobar la validez de los punteros

El siguiente ejemplo usa _CrtIsValidPointer para comprobar si un determinado intervalo de memoria es válido para lectura o escritura.

_ASSERTE(_CrtIsValidPointer( address, size, TRUE );

El siguiente ejemplo utiliza _CrtIsValidHeapPointer para comprobar si un puntero apunta a la memoria del montón local, que es el montón creado y administrado por esta instancia de la biblioteca en tiempo de ejecución de C (una DLL puede tener su propia instancia de la biblioteca y, por tanto, su propio montón, independiente del montón de la aplicación). Esta aserción captura no solo direcciones null o fuera de límites, sino también punteros a variables estáticas, variables de pila y cualquier otra memoria no local.

_ASSERTE(_CrtIsValidHeapPointer( myData );

Comprobar un bloque de memoria

El siguiente ejemplo utiliza _CrtIsMemoryBlock para comprobar si un bloque de memoria se encuentra en el montón local y tiene un tipo de bloque válido.

_ASSERTE(_CrtIsMemoryBlock (myData, size, &requestNumber, &filename, &linenumber));

En este tema

Aserciones de MFC

MFC define la macro ASSERT para comprobar aserciones. También define los métodos MFC ASSERT_VALID y CObject::AssertValid para comprobar el estado interno de un objeto derivado de CObject.

Si el argumento de la macro ASSERT de MFC se evalúa como cero o false, la macro detiene la ejecución del programa y alerta al usuario; de lo contrario, la ejecución continúa.

Cuando se produce un error de aserción, un cuadro de mensaje muestra el nombre del archivo de código fuente y el número de línea de la aserción. Si se elige Reintentar en el cuadro de diálogo, una llamada a AfxDebugBreak hace que la ejecución se interrumpa y se inicie el depurador. En ese punto, se puede examinar la pila de llamadas y utilizar otros servicios del depurador para determinar la causa de la aserción. Si la depuración Just-In-Time está habilitada, y el depurador no estaba en ejecución, el cuadro de diálogo puede iniciar el depurador.

En el ejemplo siguiente se muestra cómo utilizar ASSERT para comprobar el valor devuelto de una función:

int x = SomeFunc(y);
ASSERT(x >= 0);   //  Assertion fails if x is negative

Se puede utilizar ASSERT con la función IsKindOf para proporcionar la comprobación de tipos de los argumentos de una función:

ASSERT( pObject1->IsKindOf( RUNTIME_CLASS( CPerson ) ) );

La macro ASSERT no produce código en la versión de lanzamiento. Si necesita evaluar la expresión en la versión de lanzamiento, utilice la macro VERIFY en vez de ASSERT.

MFC ASSERT_VALID y CObject::AssertValid

El método CObject::AssertValid proporciona comprobaciones en tiempo de ejecución del estado interno de un objeto. Aunque no es obligatorio reemplazar AssertValid al derivar la clase de CObject, puede conseguir una clase más confiable si lo hace. AssertValid debe realizar aserciones en todas variables miembro del objeto para comprobar que contienen valores válidos. Por ejemplo, debería comprobar que las variables miembro no sean NULL.

En el ejemplo siguiente, se muestra cómo declarar una función AssertValid:

class CPerson : public CObject
{
protected:
    CString m_strName;
    float   m_salary;
public:
#ifdef _DEBUG
    // Override
    virtual void AssertValid() const;
#endif
    // ...
};

Cuando invalide AssertValid, llame a la versión de la clase base de AssertValid antes de ejecutar sus propias comprobaciones. Después, use la macro ASSERT para comprobar los miembros únicos de la clase derivada, como se muestra a continuación:

#ifdef _DEBUG
void CPerson::AssertValid() const
{
    // Call inherited AssertValid first.
    CObject::AssertValid();

    // Check CPerson members...
    // Must have a name.
    ASSERT( !m_strName.IsEmpty());
    // Must have an income.
    ASSERT( m_salary > 0 );
}
#endif

Si cualquiera de las variables miembro almacenan objetos, se puede utilizar la macro ASSERT_VALID para probar su validez interna (si sus clases reemplazan la función AssertValid).

Por ejemplo, considere una clase CMyData, la cual almacena un objeto CObList en una de sus variables miembro. La variable CObList, m_DataList, almacena una colección de objetos CPerson. Una declaración abreviada de CMyData tiene el siguiente aspecto:

class CMyData : public CObject
{
    // Constructor and other members ...
    protected:
        CObList* m_pDataList;
    // Other declarations ...
    public:
#ifdef _DEBUG
        // Override:
        virtual void AssertValid( ) const;
#endif
    // And so on ...
};

El sustituto de AssertValid en CMyData es:

#ifdef _DEBUG
void CMyData::AssertValid( ) const
{
    // Call inherited AssertValid.
    CObject::AssertValid( );
    // Check validity of CMyData members.
    ASSERT_VALID( m_pDataList );
    // ...
}
#endif

CMyData utiliza el mecanismo AssertValid para probar la validez de los objetos almacenados en su miembro de datos. El sustituto AssertValid de CMyData invoca la macro ASSERT_VALID para su propia variable miembro m_pDataList.

Las pruebas de validez no se detienen en este nivel, ya que la clase CObList también reemplaza la función AssertValid. Esta sustitución ejecuta pruebas de validez adicionales sobre el estado interno de la lista. Así, una prueba de validez sobre un objeto CMyData conduce a pruebas de validez adicionales sobre los estados internos del objeto de lista CObList almacenado.

Con algo más de trabajo, se podrían también agregar pruebas de validez para los objetos CPerson almacenados en la lista. Se podría derivar una clase CPersonList de CObList y reemplazar AssertValid. En el método reemplazado, se llamaría a CObject::AssertValid y, a continuación, la lista se recorrería en iteración, llamando a AssertValid para cada objeto CPerson almacenado en la lista. La clase CPerson mostrada al principio de este tema ya reemplaza la función AssertValid.

Se trata de un mecanismo muy eficaz de las versiones de depuración. Cuando, posteriormente, se compilan versiones de lanzamiento, el mecanismo se desactiva automáticamente.

Limitaciones de AssertValid

Si se desencadena una aserción, el objeto es definitivamente defectuoso y la ejecución se detendrá. Sin embargo, una falta de aserción solo indica que no se encontró ningún problema, pero no garantiza que el objeto sea correcto.

En este tema

Usar aserciones

Capturar errores lógicos

Se puede definir una aserción sobre una condición que debe ser cierta según la lógica del programa. La aserción no tiene ningún efecto a menos que se produzca un error de lógica.

Por ejemplo, suponga que está simulando moléculas de gas en un contenedor y que la variable numMols representa el número total de moléculas. Este número no puede ser menor que cero, por tanto, se podría incluir una instrucción de aserción de MFC como esta:

ASSERT(numMols >= 0);

O bien, podría incluir una aserción de CRT como esta:

_ASSERT(numMols >= 0);

Estas instrucciones no hacen nada si el programa funciona correctamente. Pero si un error lógico hace que numMols sea menor que cero, la aserción detiene la ejecución del programa y muestra el cuadro de diálogo Error de aserción.

En este tema

Comprobar resultados

Las aserciones son valiosas para probar operaciones cuyos resultados no son obvios con una simple inspección visual.

Por ejemplo, considere el siguiente código, que actualiza la variable iMols según el contenido de la lista vinculada a la que apunta mols:

/* This code assumes that type has overloaded the != operator
 with const char *
It also assumes that H2O is somewhere in that linked list.
Otherwise we'll get an access violation... */
while (mols->type != "H2O")
{
    iMols += mols->num;
    mols = mols->next;
}
ASSERT(iMols<=numMols); // MFC version
_ASSERT(iMols<=numMols); // CRT version

El número de moléculas contadas por iMols debe ser siempre menor o igual que el número total de moléculas, numMols. La inspección visual del bucle no muestra que este sea necesariamente el caso, por lo que se utiliza una instrucción de aserción después del bucle para probar esa condición.

En este tema

Buscar errores no controlados

Se pueden utilizar aserciones para probar condiciones de error en un punto del código en el que cualquier error se debería haber controlado. En el siguiente ejemplo, una rutina gráfica devuelve cero si no hay error, o un código de error, en caso contrario.

myErr = myGraphRoutine(a, b);

/* Code to handle errors and
   reset myErr if successful */

ASSERT(!myErr); -- MFC version
_ASSERT(!myErr); -- CRT version

Si el código de tratamiento del error funciona correctamente, el error debería recibir el tratamiento adecuado y myErr se debería restablecer a cero antes de alcanzar la aserción. Si myErr tiene otro valor, se produce un error en la aserción, el programa se detiene y aparece el cuadro de diálogo Error de aserción.

No obstante, las instrucciones de aserción no son un sustituto del código de control de errores. El siguiente ejemplo muestra una instrucción de aserción que puede causar problemas en el código final de la versión de lanzamiento:

myErr = myGraphRoutine(a, b);

/* No Code to handle errors */

ASSERT(!myErr); // Don't do this!
_ASSERT(!myErr); // Don't do this, either!

Este código se basa en la instrucción de aserción para controlar la condición de error. Como resultado, ningún código de error devuelto por myGraphRoutine dispondrá de tratamiento en el código de la versión de lanzamiento.

En este tema