Afirmación estática en C

¿Cuál es la mejor manera de lograr afirmaciones estáticas en tiempo de comstackción en C (no en C ++), con especial énfasis en GCC?

El estándar C11 agrega la palabra clave _Static_assert .

Esto se implementa desde gcc-4.6 :

 _Static_assert (0, "assert1"); /* { dg-error "static assertion failed: \"assert1\"" } */ 

La primera ranura debe ser una expresión constante integral. La segunda ranura es una cadena constante literal que puede ser larga ( _Static_assert(0, L"assertion of doom!") ).

Debo señalar que esto también se implementa en las versiones recientes de clang.

Esto funciona en función y scope no funcional (pero no dentro de estructuras, uniones).

 #define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1] STATIC_ASSERT(1,this_should_be_true); int main() { STATIC_ASSERT(1,this_should_be_true); } 
  1. Si la aserción del tiempo de comstackción no se puede hacer coincidir, GCC genera un mensaje casi inteligible sas.c:4: error: size of array 'static_assertion_this_should_be_true' is negative

  2. La macro podría o debería cambiarse para generar un nombre único para typedef (es decir, concatenar __LINE__ al final del nombre static_assert_... )

  3. En lugar de un ternario, esto podría usarse también #define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[2*(!!(COND))-1] que funciona incluso en el oxidado cc65 (para el comstackdor de 6502 CPU).

ACTUALIZACIÓN: Para completar, aquí está la versión con __LINE__

 #define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(!!(COND))*2-1] // token pasting madness: #define COMPILE_TIME_ASSERT3(X,L) STATIC_ASSERT(X,static_assertion_at_line_##L) #define COMPILE_TIME_ASSERT2(X,L) COMPILE_TIME_ASSERT3(X,L) #define COMPILE_TIME_ASSERT(X) COMPILE_TIME_ASSERT2(X,__LINE__) COMPILE_TIME_ASSERT(sizeof(long)==8); int main() { COMPILE_TIME_ASSERT(sizeof(int)==4); } 

ACTUALIZACIÓN2: código específico de GCC

GCC 4.3 (supongo) introdujo los atributos de la función “error” y “advertencia”. Si una llamada a una función con ese atributo no puede eliminarse mediante la eliminación del código muerto (u otras medidas), se genera un error o advertencia. Esto se puede usar para hacer que el tiempo de comstackción se base en descripciones de fallas definidas por el usuario. Queda por determinar cómo se pueden usar en el ámbito del espacio de nombres sin recurrir a una función ficticia:

 #define CTC(X) ({ extern int __attribute__((error("assertion failure: '" #X "' not true"))) compile_time_check(); ((X)?0:compile_time_check()),0; }) // never to be called. static void my_constraints() { CTC(sizeof(long)==8); CTC(sizeof(int)==4); } int main() { } 

Y así es como se ve:

 $ gcc-mp-4.5 -m32 sas.c sas.c: In function 'myc': sas.c:7:1: error: call to 'compile_time_check' declared with attribute error: assertion failure: `sizeof(int)==4` not true 

cl

Sé que la pregunta menciona explícitamente a gcc, pero para completarlo aquí hay un ajuste para los comstackdores de Microsoft.

El uso de la matriz de tamaño negativo typedef no persuade a cl para escupir un error decente. Simplemente dice el error C2118: negative subscript . Un campo de bits de ancho cero tiene mejores resultados a este respecto. Como esto implica deshacerse de una estructura, necesitamos usar nombres de tipos únicos. __LINE__ no corta la mostaza: es posible tener un COMPILE_TIME_ASSERT() en la misma línea en un encabezado y un archivo fuente, y su comstackción se romperá. __COUNTER__ viene al rescate (y ha estado en gcc desde 4.3).

 #define CTASTR2(pre,post) pre ## post #define CTASTR(pre,post) CTASTR2(pre,post) #define STATIC_ASSERT(cond,msg) \ typedef struct { int CTASTR(static_assertion_failed_,msg) : !!(cond); } \ CTASTR(static_assertion_failed_,__COUNTER__) 

Ahora

 STATIC_ASSERT(sizeof(long)==7, use_another_compiler_luke) 

bajo cl da:

error C2149: ‘static_assertion_failed_use_another_compiler_luke’: el campo de bit nombrado no puede tener un ancho cero

Gcc también da un mensaje inteligible:

error: ancho cero para el campo de bit ‘static_assertion_failed_use_another_compiler_luke’

De la Wikipedia :

 #define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;} COMPILE_TIME_ASSERT( BOOLEAN CONDITION ); 

Si se utiliza la macro STATIC_ASSERT () con __LINE__ , es posible evitar conflictos de números de línea entre una entrada en un archivo .c y una entrada diferente en un archivo de encabezado incluyendo __INCLUDE_LEVEL__ .

Por ejemplo :

 /* Trickery to create a unique variable name */ #define BOOST_JOIN( X, Y ) BOOST_DO_JOIN( X, Y ) #define BOOST_DO_JOIN( X, Y ) BOOST_DO_JOIN2( X, Y ) #define BOOST_DO_JOIN2( X, Y ) X##Y #define STATIC_ASSERT(x) typedef char \ BOOST_JOIN( BOOST_JOIN(level_,__INCLUDE_LEVEL__), \ BOOST_JOIN(_assert_on_line_,__LINE__) ) [(x) ? 1 : -1] 

La forma clásica es usar una matriz:

 char int_is_4_bytes_assertion[sizeof(int) == 4 ? 1 : -1]; 

Funciona porque si la aserción es verdadera, la matriz tiene el tamaño 1 y es válida, pero si es falsa, el tamaño de -1 genera un error de comstackción.

La mayoría de los comstackdores mostrarán el nombre de la variable y señalarán la parte derecha del código donde puede dejar comentarios finales sobre la afirmación.

Para aquellos de ustedes que desean algo realmente básico y portátil, pero no tienen acceso a las características de C ++ 11, he escrito exactamente el tema.
Use STATIC_ASSERT normalmente (puede escribirlo dos veces en la misma función si lo desea) y use GLOBAL_STATIC_ASSERT fuera de las funciones con una frase única como primer parámetro.

 #if defined(static_assert) # define STATIC_ASSERT static_assert # define GLOBAL_STATIC_ASSERT(a, b, c) static_assert(b, c) #else # define STATIC_ASSERT(pred, explanation); {char assert[1/(pred)];(void)assert;} # define GLOBAL_STATIC_ASSERT(unique, pred, explanation); namespace ASSERTATION {char unique[1/(pred)];} #endif GLOBAL_STATIC_ASSERT(first, 1, "Hi"); GLOBAL_STATIC_ASSERT(second, 1, "Hi"); int main(int c, char** v) { (void)c; (void)v; STATIC_ASSERT(1 > 0, "yo"); STATIC_ASSERT(1 > 0, "yo"); // STATIC_ASSERT(1 > 2, "yo"); //would compile until you uncomment this one return 0; } 

Explicación:
Primero verifica si tiene la afirmación real, que definitivamente querría utilizar si está disponible.
Si no lo hace, afirma obteniendo su predicación y dividiéndola por sí mismo. Esto hace dos cosas.
Si es cero, id est, la aserción ha fallado, causará un error de división por cero (la aritmética es forzada porque está tratando de declarar una matriz).
Si no es cero, normaliza el tamaño de la matriz a 1 . Entonces, si la afirmación pasó, no querría que fallara de todos modos porque su predicado se evaluó a -1 (inválido), o sea 232442 (pérdida masiva de espacio, IDK si se optimizaría).
Para STATIC_ASSERT está envuelto en llaves, esto lo convierte en un bloque, que alcanza la variable assert , lo que significa que puede escribirlo muchas veces.
También lo arroja al void , que es una forma conocida de deshacerse de unused variable advertencias unused variable .
Para GLOBAL_STATIC_ASSERT , en lugar de estar en un bloque de código, genera un espacio de nombre. Los espacios de nombres están permitidos fuera de las funciones. Se requiere unique identificador unique para detener cualquier definición conflictiva si usa esta más de una vez.


Trabajé para mí en GCC y VS’12 C ++

Esto funciona, con el conjunto de opciones “eliminar sin usar”. Puedo usar una función global para verificar los parámetros globales.

 // #ifndef __sassert_h__ #define __sassert_h__ #define _cat(x, y) x##y #define _sassert(exp, ln) \ extern void _cat(ASSERT_WARNING_, ln)(void); \ if(!(exp)) \ { \ _cat(ASSERT_WARNING_, ln)(); \ } #define sassert(exp) _sassert(exp, __LINE__) #endif //__sassert_h__ //----------------------------------------- static bool tab_req_set_relay(char *p_packet) { sassert(TXB_TX_PKT_SIZE < 3000000); sassert(TXB_TX_PKT_SIZE >= 3000000); ... } //----------------------------------------- Building target: ntank_app.elf Invoking: Cross ARM C Linker arm-none-eabi-gcc ... ../Sources/host_if/tab_if.c:637: undefined reference to `ASSERT_WARNING_637' collect2: error: ld returned 1 exit status make: *** [ntank_app.elf] Error 1 // 

Esto funcionó para algunos viejos gcc. Perdón por haber olvidado qué versión era:

 #define _cat(x, y) x##y #define _sassert(exp, ln)\ extern char _cat(SASSERT_, ln)[1]; \ extern char _cat(SASSERT_, ln)[exp ? 1 : 2] #define sassert(exp) _sassert((exp), __LINE__) // sassert(1 == 2); // #148 declaration is incompatible with "char SASSERT_134[1]" (declared at line 134) main.c /test/source/controller line 134 C/C++ Problem 

NO recomendaría usar la solución usando un typedef :

 #define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1] 

No se garantiza que la statement de matriz con la palabra clave typedef se evalúe en tiempo de comstackción. Por ejemplo, se comstackrá el siguiente código en el scope del bloque:

 int invalid_value = 0; STATIC_ASSERT(invalid_value, this_should_fail_at_compile_time_but_will_not); 

Yo recomendaría esto en su lugar (en C99):

 #define STATIC_ASSERT(COND,MSG) static int static_assertion_##MSG[(COND)?1:-1] 

Debido a la palabra clave static , la matriz se definirá en tiempo de comstackción. Tenga en cuenta que esta afirmación solo funcionará con COND que se evalúan en tiempo de comstackción. No funcionará (es decir, la comstackción fallará) con condiciones basadas en valores en la memoria, como los valores asignados a las variables.

Intereting Posts