Maneras de ASEGURAR expresiones en tiempo de comstackción en C

Estoy ordenando un código anterior que usa ‘números mágicos’ para establecer registros de hardware, y me gustaría utilizar constantes en lugar de estos números para hacer que el código sea algo más expresivo (de hecho, se asignarán a los nombres / valores utilizados para documentar los registros).

Sin embargo, me preocupa que con el volumen de cambios pueda romper los números mágicos. Aquí hay un ejemplo simplificado (el conjunto de registros es más complejo):

const short mode0 = 0; const short mode1 = 1; const short mode2 = 2; const short state0 = 0; const short state1 = 4; const short state2 = 8; 

Entonces, en lugar de:

 set_register(5); 

tenemos:

 set_register(state1|mode1); 

Lo que estoy buscando es una versión de comstackción de:

 ASSERT(5==(state1|mode1)); 

Actualizar

@Christian, gracias por la respuesta rápida, estoy interesado en una respuesta del entorno C / non-boost también porque este es el código del controlador / kernel.

NUEVA RESPUESTA :

En mi respuesta original (a continuación), tuve que tener dos macros diferentes para admitir las afirmaciones en un scope de función y en el scope global. Me preguntaba si era posible encontrar una solución única que funcionara en ambos ámbitos.

Pude encontrar una solución que funcionaba para comstackdores de Visual Studio y Comeau utilizando matrices de caracteres externos. Pero pude encontrar una solución más compleja que funciona para GCC. Pero la solución de GCC no funciona para Visual Studio. 🙁 Pero agregando un ‘#ifdef __ GNUC __’, es fácil elegir el conjunto adecuado de macros para un comstackdor determinado.

Solución:

 #ifdef __GNUC__ #define STATIC_ASSERT_HELPER(expr, msg) \ (!!sizeof \ (struct { unsigned int STATIC_ASSERTION__##msg: (expr) ? 1 : -1; })) #define STATIC_ASSERT(expr, msg) \ extern int (*assert_function__(void)) [STATIC_ASSERT_HELPER(expr, msg)] #else #define STATIC_ASSERT(expr, msg) \ extern char STATIC_ASSERTION__##msg[1]; \ extern char STATIC_ASSERTION__##msg[(expr)?1:2] #endif /* #ifdef __GNUC__ */ 

Aquí están los mensajes de error reportados para STATIC_ASSERT(1==1, test_message); en la línea 22 de test.c:

GCC:

 line 22: error: negative width in bit-field `STATIC_ASSERTION__test_message' 

Estudio visual:

 test.c(22) : error C2369: 'STATIC_ASSERTION__test_message' : redefinition; different subscripts test.c(22) : see declaration of 'STATIC_ASSERTION__test_message' 

Comeau:

 line 22: error: declaration is incompatible with "char STATIC_ASSERTION__test_message[1]" (declared at line 22) 

RESPUESTA ORIGINAL :

Hago algo muy similar a lo que hace Checkers. Pero incluyo un mensaje que aparecerá en muchos comstackdores:

 #define STATIC_ASSERT(expr, msg) \ { \ char STATIC_ASSERTION__##msg[(expr)?1:-1]; \ (void)STATIC_ASSERTION__##msg[0]; \ } 

Y para hacer algo en el ámbito global (fuera de una función) usa esto:

 #define GLOBAL_STATIC_ASSERT(expr, msg) \ extern char STATIC_ASSERTION__##msg[1]; \ extern char STATIC_ASSERTION__##msg[(expr)?1:2] 

Hay un artículo de Ralf Holly que examina diferentes opciones de afirmaciones estáticas en C.

Él presenta tres enfoques diferentes:

  • los valores de mayúsculas deben ser únicos
  • las matrices no deben tener dimensiones negativas
  • división por cero para expresiones constantes

Su conclusión para la mejor implementación es esta:

 #define assert_static(e) \ do { \ enum { assert_static__ = 1/(e) }; \ } while (0) 

Afirmación estática del boost de Checkout

Puede rodar su propia afirmación estática si no tiene acceso a la función de afirmación estática de una biblioteca de terceros (como boost):

 #define STATIC_ASSERT(x) \ do { \ const static char dummy[(x)?1:-1] = {0};\ } while(0) 

La desventaja es, por supuesto, que el mensaje de error no va a ser muy útil, pero al menos, le dará el número de línea.

 #define static_assert(expr) \ int __static_assert(int static_assert_failed[(expr)?1:-1]) 

Se puede usar en cualquier lugar, en cualquier momento. Creo que es la solución más fácil.

Antes del uso, pruébelo con su comstackdor cuidadosamente.

Cualquiera de las técnicas enumeradas aquí debería funcionar y cuando C ++ 0x esté disponible, podrá usar la palabra clave static_assert .

Si tiene Boost, entonces usar BOOST_STATIC_ASSERT es el camino a seguir. Si está usando C o no quiere obtener Boost aquí está mi archivo c_assert.h que define (y explica el funcionamiento de) unas pocas macros para manejar las aserciones estáticas.

Es un poco más intrincado que debería ser porque en el código ANSI C necesita 2 macros diferentes, una que puede funcionar en el área donde tiene declaraciones y otra que puede funcionar en el área donde van las declaraciones normales. También hay un poco de trabajo que se dedica a hacer que la macro funcione a nivel global o en el scope del bloque y un montón de mugre para garantizar que no haya colisiones de nombres.

STATIC_ASSERT() se puede usar en el bloque de statement de variable o scope global.

STATIC_ASSERT_EX() puede estar entre las declaraciones regulares.

Para código C ++ (o código C99 que permite declaraciones mezcladas con declaraciones) STATIC_ASSERT() funcionará en cualquier lugar.

 /* Define macros to allow compile-time assertions. If the expression is false, an error something like test.c(9) : error XXXXX: negative subscript will be issued (the exact error and its format is dependent on the compiler). The techique used for C is to declare an extern (which can be used in file or block scope) array with a size of 1 if the expr is TRUE and a size of -1 if the expr is false (which will result in a compiler error). A counter or line number is appended to the name to help make it unique. Note that this is not a foolproof technique, but compilers are supposed to accept multiple identical extern declarations anyway. This technique doesn't work in all cases for C++ because extern declarations are not permitted inside classes. To get a CPP_ASSERT(), there is an implementation of something similar to Boost's BOOST_STATIC_ASSERT(). Boost's approach uses template specialization; when expr evaluates to 1, a typedef for the type ::interslice::StaticAssert_test< sizeof( ::interslice::StaticAssert_failed) > which boils down to ::interslice::StaticAssert_test< 1> which boils down to struct StaticAssert_test is declared. If expr is 0, the compiler will be unable to find a specialization for ::interslice::StaticAssert_failed. STATIC_ASSERT() or C_ASSERT should work in either C or C++ code (and they do the same thing) CPP_ASSERT is defined only for C++ code. Since declarations can only occur at file scope or at the start of a block in standard C, the C_ASSERT() or STATIC_ASSERT() macros will only work there. For situations where you want to perform compile-time asserts elsewhere, use C_ASSERT_EX() or STATIC_ASSERT_X() which wrap an enum declaration inside it's own block. */ #ifndef C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546 #define C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546 /* first some utility macros to paste a line number or counter to the end of an identifier * this will let us have some chance of generating names that are unique * there may be problems if a static assert ends up on the same line number in different headers * to avoid that problem in C++ use namespaces */ #if !defined( PASTE) #define PASTE2( x, y) x##y #define PASTE( x, y) PASTE2( x, y) #endif /* PASTE */ #if !defined( PASTE_LINE) #define PASTE_LINE( x) PASTE( x, __LINE__) #endif /* PASTE_LINE */ #if!defined( PASTE_COUNTER) #if (_MSC_VER >= 1300) /* __COUNTER__ introduced in VS 7 (VS.NET 2002) */ #define PASTE_COUNTER( x) PASTE( x, __COUNTER__) /* __COUNTER__ is a an _MSC_VER >= 1300 non-Ansi extension */ #else #define PASTE_COUNTER( x) PASTE( x, __LINE__) /* since there's no __COUNTER__ use __LINE__ as a more or less reasonable substitute */ #endif #endif /* PASTE_COUNTER */ #if __cplusplus extern "C++" { // required in case we're included inside an extern "C" block namespace interslice { template struct StaticAssert_failed; template<> struct StaticAssert_failed { enum {val = 1 }; }; template struct StaticAssert_test { }; } } #define CPP_ASSERT( expr) typedef ::interslice::StaticAssert_test< sizeof( ::interslice::StaticAssert_failed< (bool) (expr) >) > PASTE_COUNTER( IntersliceStaticAssertType_) #define STATIC_ASSERT( expr) CPP_ASSERT( expr) #define STATIC_ASSERT_EX( expr) CPP_ASSERT( expr) #else #define C_ASSERT_STORAGE_CLASS extern /* change to typedef might be needed for some compilers? */ #define C_ASSERT_GUID 4964f7ac50fa4661a1377e4c17509495 /* used to make sure our extern name doesn't collide with something else */ #define STATIC_ASSERT( expr) C_ASSERT_STORAGE_CLASS char PASTE( PASTE( c_assert_, C_ASSERT_GUID), [(expr) ? 1 : -1]) #define STATIC_ASSERT_EX(expr) do { enum { c_assert__ = 1/((expr) ? 1 : 0) }; } while (0) #endif /* __cplusplus */ #if !defined( C_ASSERT) /* C_ASSERT() might be defined by winnt.h */ #define C_ASSERT( expr) STATIC_ASSERT( expr) #endif /* !defined( C_ASSERT) */ #define C_ASSERT_EX( expr) STATIC_ASSERT_EX( expr) #ifdef TEST_IMPLEMENTATION C_ASSERT( 1 < 2); C_ASSERT( 1 < 2); int main( ) { C_ASSERT( 1 < 2); C_ASSERT( 1 < 2); int x; x = 1 + 4; C_ASSERT_EX( 1 < 2); C_ASSERT_EX( 1 < 2); return( 0); } #endif /* TEST_IMPLEMENTATION */ #endif /* C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546 */ 

Tratar:

 #define STATIC_ASSERT(x, error) \ do { \ static const char error[(x)?1:-1];\ } while(0) 

Entonces puedes escribir:

 STATIC_ASSERT(a == b, a_not_equal_to_b); 

Lo que puede darle un mejor mensaje de error (dependiendo de su comstackdor).

También hay un examen muy completo de las técnicas de STATIC_ASSERT en el diseño moderno de C ++ de Alexandrescu, ISBN 978-0201704310.

La opción común y portátil es

 #if 5 != (state1|mode1) # error "aaugh!" #endif 

pero no funciona en este caso, porque son constantes C y no #define s.

Puede ver la macro BUILD_BUG_ON del kernel de BUILD_BUG_ON para algo que maneja su caso:

 #define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)])) 

Cuando la condition es verdadera, esto se convierte en ((void)sizeof(char[-1])) , que es ilegal y debe fallar en el momento de la comstackción, y de lo contrario se convierte en ((void)sizeof(char[1])) , que es esta bien

Asegúrese de comstackr con un comstackdor suficientemente reciente (por ejemplo, gcc -std=c11 ).

Entonces su statement es simplemente:

 _Static_assert(state1|mode1 == 5, "Unexpected change of bitflags");