failed - ¿Dónde se debe utilizar() en C resp. C++?
assertion failed c++ solucion (4)
Creo que hay un punto simple y poderoso para hacer:
assert ()
es para verificar la consistencia interna .
Úselo para verificar condiciones previas, condiciones posteriores e invariantes.
Cuando puede haber una inconsistencia debida a factores externos , circunstancias que el código no puede controlar localmente, se produce una excepción. Las excepciones son para cuando las condiciones posteriores no puedan cumplirse dadas las condiciones previas. Buenos ejemplos:
-
new int
está bien hasta sus condiciones previas, por lo que si la memoria no está disponible, lanzar es la única respuesta razonable. (La condición posterior demalloc
es "un puntero válido o NULL ") - La condición posterior de un constructor es la existencia de un objeto cuyos invariantes están establecidos. Si no puede construir un estado válido, lanzar es la única respuesta razonable.
assert
no debe utilizarse para lo anterior. Por el contrario,
void sort (int * begin, int * end) {
// assert (begin <= end); // OPTIONAL precondition, possibly want to throw
for (int * i = begin, i < end; ++i) {
assert (is_sorted (begin, i)); // invariant
// insert *i into sorted position ...
}
}
La comprobación de is_sorted
está comprobando que el algoritmo se está comportando correctamente dadas sus condiciones previas . Una excepción no es una respuesta razonable.
Para abreviar una larga historia: assert
es para cosas que NUNCA sucederán SI el programa es LOCALMENTE correcto, las excepciones son para cosas que pueden salir mal incluso cuando el código es correcto.
Si las entradas no válidas activan o no las excepciones es una cuestión de estilo.
¿En qué lugares deberíamos usar la función assert()
específicamente? Si es una situación como determinar si un valor entero es mayor que cero o un puntero es nulo, simplemente podemos usar una función privada para verificar esto. En este tipo de situación, ¿dónde deberíamos usar assert()
sobre un cheque escrito personalizado?
Normalmente lo usa cuando quiere que el programa aborte y muestre un error de tiempo de ejecución si una condición booleana no es verdadera. Normalmente se usa así:
void my_func( char* str )
{
assert ( str != NULL );
/* code */
}
También se puede utilizar con funciones que devuelven un puntero NULL en caso de error:
SDL_Surface* screen = SDL_SetVideoMode( 640, 480, 16, SDL_HWSURFACE );
assert ( screen != NULL );
El mensaje de error exacto que proporciona assert()
depende de su compilador, pero generalmente sigue estas líneas:
Assertion failed: str, mysrc.c, line 5
Voy a tirar mi punto de vista de assert()
. Puedo encontrar lo que assert()
hace en otro lugar, pero proporciona un buen foro para sugerencias sobre cómo y cuándo usarlo.
Tanto assert
como static_assert
sirven funciones similares. Digamos que tienes alguna función foo
. Por ejemplo, digamos que tiene una función foo(void*)
que asume que su argumento no es nulo:
void foo(void* p) {
assert(p);
...
}
Tu función tiene un par de personas que se preocupan por ello.
Primero, el desarrollador que llama a tu función. Puede que solo mire su documentación y tal vez se pierda la parte de no permitir un puntero nulo como argumento. Es posible que nunca lea el código de la función, pero cuando lo ejecuta en modo de depuración, el asertivo puede detectar el uso inapropiado de su función (especialmente si sus casos de prueba son buenos).
Segundo (y más importante), es el desarrollador que lee tu código. Para él, su afirmación dice que después de esta línea, p no es nulo . Esto es algo que a veces se pasa por alto, pero creo que es la característica más útil de la macro assert
. Documenta y hace cumplir las condiciones.
Debe usar assert
s para codificar esta información siempre que sea práctico. Me gusta pensar que dice "en este punto del código, esto es cierto" (y lo dice de una manera mucho más fuerte de lo que lo haría un comentario). Por supuesto, si tal declaración no transmite mucha o ninguna información, entonces no es necesaria.
Contexto: escribo software de servidor para ganarme la vida, del tipo que permanece activo durante semanas antes de que se cargue la próxima versión. Así que mis respuestas pueden ser desviadas hacia un código altamente defensivo.
El principio.
Antes de profundizar en los aspectos específicos de dónde utilizar assert
, es importante comprender el principio que hay detrás.
assert
es una herramienta esencial en la Programación Defensiva . Ayuda a validar las suposiciones (hacerlas valer en realidad) y, por lo tanto, detectar errores de programación (para distinguirlos de los errores del usuario). El objetivo de assert
es detectar situaciones erróneas, de las cuales la recuperación generalmente no es posible de inmediato.
Ejemplo:
char const* strstr(char const* haystack, char const* needle) {
assert(haystack); assert(needle);
// ...
}
Alternativas.
Cía ? Hay poca alternativa. A menos que su función haya sido diseñada para poder pasar un código de error o devolver un valor centinela, y esto está debidamente documentado.
En C ++, las excepciones son una alternativa perfectamente aceptable. Sin embargo, un assert
puede ayudar a generar un volcado de memoria para que pueda ver exactamente en qué estado se encuentra el programa en el momento en que se detecta la situación errónea (lo que ayuda a la depuración), mientras que una excepción desenrollará la pila y, por lo tanto, perderá el contexto (oups). ...).
Además, una excepción podría (desafortunadamente) ser atrapada por un manejador de alto nivel (o una captura desagradable de otro desarrollador (por supuesto, no haría eso)), en cuyo caso podría pasar por alto el error hasta que sea demasiado tarde.
Donde NO usarlo.
Primero, debe entenderse que assert
solo es útil en el código de depuración . En Release, NDEBUG está definido y no se genera ningún código. Como corolario, en Release assert
tiene el mismo valor que un comentario.
- Nunca lo utilice para verificaciones que sean necesarias para el buen comportamiento del software. Las condiciones de error deben ser revisadas y tratadas. Siempre.
En segundo lugar, debe entenderse que la entrada mal formada es parte de su vida. ¿Desea que su compilador muestre un mensaje de assert
cada vez que cometa un error? ¡Tararear! Por lo tanto:
- Nunca lo use para la validación de datos de entrada. Los datos de entrada deben validarse y los errores deben informarse adecuadamente al usuario. Siempre.
En tercer lugar, debe entenderse que los choques no son apreciados. Se espera de su programa que se ejecute sin problemas. Por lo tanto, uno no debe tener la tentación de dejar activaciones en el modo de lanzamiento: el código de lanzamiento termina en las manos del usuario final y nunca debe fallar nunca. En el peor de los casos, debería apagarse mientras se muestra un mensaje de error. Se espera que no se pierda ningún dato de usuario durante este proceso, e incluso mejor si al reiniciarlo se lleva al lugar donde estaba: eso es lo que hacen los navegadores modernos, por ejemplo.
- Nunca dejes afirmaciones en Release.
Nota: para el código del servidor, al "golpear" una afirmación, logramos volver a la posición para tratar la siguiente consulta en la mayoría de los casos.
Donde usarlo.
assert
está activado en el modo de depuración, por lo que debe utilizarse para la depuración. Cada vez que prueba un nuevo código, cada vez que se ejecuta su suite de pruebas, cada vez que el software está en sus manos (o en las de sus compañeros de equipo), cada vez que el software está en sus manos, el departamento de control de calidad. Los avisos le permiten detectar errores y le brinda el contexto completo del error para que pueda repararlos .
- Úsalo durante los ciclos de desarrollo y prueba.
Aun mejor. Dado que sabe que el código no se ejecutará en la versión, puede permitirse realizar verificaciones costosas .
Nota: también debe probar el binario de lanzamiento, solo para comprobar el rendimiento.
¿Y en Liberación?
Bueno, en el código base sobre el que trabajo, reemplazamos las aserciones de bajo costo (las demás se ignoran) por excepciones específicas que solo son detectadas por un controlador de alto nivel que registrará el problema (con retroceso), devolverá una respuesta de error precodificada y reanudar el servicio. El equipo de desarrollo es notificado automáticamente.
En el software que se implementa, las mejores prácticas que he visto implican crear un volcado de memoria y transmitirlo a los desarrolladores para que lo analicen mientras intentan no perder ningún dato de usuario y se comportan de la manera más cortés posible con el usuario desafortunado. Me siento realmente bendecido de estar trabajando en el lado del servidor cuando contemplo la dificultad de esta tarea;)