2009-12-04 17 views
5

En C y C++ assert es una rutina muy pesada muy, escribiendo un error en stdout y terminando el programa. En nuestra aplicación, hemos implementado un reemplazo mucho más robusto para assert y dado su propio macro. Se ha hecho todo lo posible para reemplazar assert con nuestra macro, sin embargo, todavía hay muchas maneras assert puede reintroducirse (por ejemplo, de bibliotecas internas de terceros, la inyección ingenuo, etc.)Ayudando a evitar la afirmación ... ¡siempre!

¿Alguna sugerencia sobre cómo podemos reducir, limitar o incluso erradicar los usos de assert? La mejor respuesta será aquella que el compilador pueda captar para nosotros, de modo que no tengamos que cuidar la base de códigos tanto como lo hacemos actualmente.

+4

¿Qué es exactamente lo malo de 'assert()' que termina el programa? –

+3

Bueno, para empezar, el programa termina dejando assert() solo válido para errores OMG USTED ROMPÓ EL UNIVERSO. ¿No sería desagradable ser el usuario y bloquear el programa porque su recuento de encabezados de informe TPS está desactivado en uno? Tal vez solo quiera que registre la afirmación fallida para que el usuario pueda continuar lo que está haciendo. Tal vez desee enviar el error de afirmación en lugar de confiar en el usuario desconcertado y molesto para saber hacer eso. – Schwern

+14

Si tus usuarios están ejecutando compilaciones de depuración, tienes problemas más serios. Las afirmaciones y el registro son cosas completamente diferentes, y no deben confundirse. –

Respuesta

13

No estoy seguro de que realmente entiendo el problema, en realidad. Las afirmaciones solo son costosas si se apagan, lo cual está bien de todos modos, ya que ahora se encuentra en una situación de excepción.

assert solo está habilitado en compilaciones de depuración, por lo tanto, utilice la compilación de lanzamiento de una biblioteca de terceros. Pero en realidad, las afirmaciones no deberían irse en todo momento.

+2

No se apagan a cada momento, pero dado el tamaño de nuestra aplicación, queremos hacer más que simplemente desaparecer en caso de que falle una afirmación. La capacidad de manejar una falla de afirmación de forma más elegante proporciona un gran beneficio tanto para QE como para el desarrollo. – fbrereto

+1

@fbrereto: Deberías lanzar excepciones en lugar de llamar a assert. – Frank

1

El enfoque más obvio parece ser dar su propia versión de afirmar su propio nombre, ligeramente diferente de assert(). Luego puede buscar el texto, ver los mensajes del vinculador, etc., para la cadena literal "_assert" y sabe que tiene un problema cuando lo ve.

En mi propio código, siempre uso Assert(), que se expande a mi propia función que realiza una aserción, o se expande a ((void)0) para la compilación de lanzamiento. El compilador convertirá la expresión ((void)0) en nada, pero aún cuenta como una expresión. Por lo tanto

Assert(3 == x); 

se convertirá en

((void)0); 

y el punto y coma tiene un lugar a donde ir.

Por cierto, una vez trabajé en una aplicación de GUI donde la afirmación era un diálogo emergente modal GUI especial. Tuviste tres opciones: Ignorar, Ignorar para siempre o Romper. Ignore ignoraría la afirmación y seguiría funcionando. Ignorar para siempre establecería un indicador, y hasta que haya reiniciado el programa en el depurador, esa afirmación ya no se disparará. La ruptura permitiría que la afirmación entre en el depurador.

No recuerdo cómo garantizaban que cada afirmación tuviera su propia bandera. ¿Tal vez cuando escribiste la llamada a Assert() tuviste que especificar un entero único? Sería bueno si fuera más automático que eso. Estoy bastante seguro de que la implementación real era un poco de vector, y establecería el bit cuando elijas ignorar para siempre.

+0

O, después de escribir "myAssert", en lugar de buscar "assert" en todas partes, simplemente "#define assert myAssert" y deje que el preprocesador haga el trabajo. NO ABOGO ESTO, solo digo que se puede hacer. – Beta

+1

@Beta: en la mayoría de los casos, ese #define funcionaría, pero para afirmarlo realmente no funcionará. Dado que el significado de 'assert()' puede cambiar (incluso en un único archivo fuente) en función de si se define NDEBUG, puede/será # undef'd y # define'd de nuevo a sus espaldas. –

+0

@Jerry Coffin: Estoy fuera de mi profundidad aquí (y mi impresión es que no hay seguridad una vez que las macros están en guerra) pero ¿no podría uno definir indefinidamente el NDEBUG, ya sea en un encabezado o en los makefiles (o ambos)? ¿Eso no detendría los intentos casuales de afirmar? – Beta

0

Encuentra assert en las cabeceras de la biblioteca (asumiendo que son archivos reales en el sistema de archivos) y sustituirla por una cosa no válida

// #define assert(condition) ... /* old definition */ 
#define assert(condition) ((condition) & "PLEASE DO NOT USE ASSERT" = 42) 
+1

Y recuerde hacer eso de nuevo (y de nuevo ...) cada vez que actualice su compilador. – pmg

+0

Para "una cosa no válida" me gusta usar: 'error #pragma' Lo bueno es que esto tiene casi el mismo efecto, ya sea que el compilador lo admita como característica o no. Y es bastante claro que identifica que tiene la intención de un error allí. – steveha

+0

@steveha: No estoy seguro de que pueda usar un error #pragma en #define. No puedo señalar la ubicación en el estándar de inmediato, pero no creo que haga lo que crees que hace. – fbrereto

1

assert() es generalmente #define 'd ser ((void)0) for release code (#define NDEBUG), por lo hay sin gastos generales.

Al usar una versión de prueba, ¿está afectando el rendimiento general a su capacidad para que las pruebas sean realistas?

+0

Motivos ver mi comentario a GMan – fbrereto

+0

Una implementación válida de 'assert()' * no puede * definirse como vacía para el código de la versión. Por ejemplo, algo como 'assert (x), y = 0;' es código legítimo, pero produciría un error de sintaxis si 'assert()' no se convirtiera en nada en un lanzamiento. Una implementación correcta se convertiría en '((void *) 0)' cuando se definió NDEBUG. –

+0

Puede simplemente usar '((void) 0)', no necesita puntero. :) – GManNickG

1

Si el código fuente está bajo su control:

#define NDEBUG 
// Before 
#include <assert.h> 
// Or other header that includes assert.h 

o el uso de encabezado precompilado o compilar opciones para definir NDEBUG.

Para los archivos binarios de terceros, utilice la versión de lanzamiento de los mismos.

2

Dependería (al menos en parte) de lo que está cambiando. Suponiendo que no le importe imprimir su mensaje normal, y sobre todo quiere deshacerse de él llamando al abort(), puede considerar dejar solo assert() y, en su lugar, definir su propia versión de abort().

En teoría, hacer eso no es portátil, pero en realidad, abort() es una función bastante normal en la biblioteca estándar, y si enlaza la suya en su lugar, obtiene su comportamiento. A veces (especialmente algunos vinculadores de Microsoft) tiene que hacer un poco de trabajo para que el enlazador coopere en el reemplazo de su abort() con el suyo, pero rara vez es muy difícil.

2

Puede ser útil mejorar la función de aserción incorporada (para proporcionar rastreos de pila, volcados de núcleo, quién sabe). En ese caso, si tiene problemas para que sus desarrolladores sigan los estándares que tenga (como "en lugar de assert() use SUPER_ASSERT()" o lo que sea), puede simplemente poner su propio encabezado assert.h en la ruta de inclusión antes del directorio de tiempo de ejecución del compilador de encabezados.

Eso garantizará que cualquier persona que use la macro estándar assert() obtendrá un error de compilación u obtendrá su funcionalidad de aserción (dependiendo de lo que tenga su encabezado assert.h).

+1

¡Ah, la prioridad de la ruta del directorio es inteligente! – fbrereto

+1

'SUPER_ASSERT' - ¿Has leído el libro de John Robbins? Recogí el hábito de John de afirmar todo el tiempo, desde el estado hasta los parámetros para devolver los valores. Mi código no puede tirar pedos ni estornudar sin que yo lo sepa. Literalmente se depura a sí mismo (gasto menos de 2 minutos debajo de un depurador al rastrear problemas). Realmente me compadezco de las personas que no los usan. Puedes decir quiénes son: son los que se concentran intensamente mientras protagonizan el depurador durante horas durante todo el día (o los que agregan todos esos 'printf's inútiles). – jww

+0

@noloader: sí, tengo la mayoría (quizás todos) de los libros de John Robbin. Definitivamente cosas buenas. –

1

Parece que se está perdiendo el hecho de que el código de terceros probablemente se haya escrito bajo la suposición de comportamiento "estándar" de assert. Es decir. el código espera que el programa finalice en una afirmación fallida. El código que sigue a la aserción normalmente no puede y no funcionará correctamente si la condición afirmada está rota. En 99 casos de cada 100, no funcionará en absoluto. En 99 casos de cada 100, simplemente se bloqueará, es decir, el programa terminará de todos modos.

para creer que reemplazando el comportamiento assert de código de terceros de alguna manera va a hacer el programa en vivo ya es ingenua en el mejor.

+1

Suponer que OP aún no sabía que es ingenuo en el mejor de los casos.¿Alguna vez se le pasó por la cabeza, ese volcado del núcleo, un seguimiento de pila, incluso un bloqueo normal proporcionará más información útil para la depuración? – hirschhornsalz

+0

@hirschhornsalz - No conozco muchas personas que puedan leer los vertederos de forma efectiva. Además, nunca es apropiado que una biblioteca de terceros bloquee su programa. La biblioteca debe procesar la solicitud o fallar la llamada; el bloqueo (o la mierda) no es una elección adecuada. – jww

2

Creo que su pregunta es totalmente válida. Si ha implementado su propio manejo de errores, es posible que desee:

  1. Activar siempre las afirmaciones incluso en compilaciones de versiones.
  2. Implementa mejores informes de errores en caso de que se active una afirmación. Es posible que desee enviar informes de errores o escribir en archivos de registro.

Habiendo dicho eso, no veo ninguna solución que siempre funcione.

  • Si tiene suerte, las bibliotecas de terceros utilizar macros afirmar que puede volver a definir por sí mismo siempre que el archivo de definición de esta macro tiene algún tipo de #pragma once o #ifndef __HEADERFILE_H__ #define __HEADERFILE_H__ disposición contra la inclusión múltiple. Incluye el archivo de cabecera por separado, redefinir ASSERT y estás bien.

  • Si incluyen directamente assert.h o cassert, solo se puede parchar el código, supongo. Realice cambios mínimos de código, guarde los cambios como archivos de parche y cuando actualice la biblioteca, tenga la esperanza de que los parches aún funcionen. Agregue los parches al control de versiones.

Si esto no funciona, vuelva a plantear la cuestión si realmente necesita afirmaciones internas en bibliotecas de terceros. Solo se crean construcciones de lanzamiento, esto elimina las afirmaciones y agrega tus ASSERT para verificar la corrección dentro de tu código. Verifique la validez de los valores de devolución. Si se activa un ASSERT de este tipo, aún puede sumergirse en el código de terceros para ver qué causó el problema.

2

Creo que la pregunta es válida.

Mi propia afirmación se expande a asm ("int3") si se activa, lo que es equivalente a un punto de interrupción. También encontré que es mucho más útil para la depuración que una simple terminación.

Simplemente lo llamé "ASSERT()" en lugar del normal "assert()" y evité el uso de assert() en absoluto.

Cuestiones relacionadas