¿Cuál es la mejor manera de lograr afirmaciones estáticas en tiempo de compilación en C (no C ++), con especial énfasis en GCC?
c
gcc
assert
compile-time
static-assert
Matt Joiner
fuente
fuente
_Static_assert
es parte del estándar C11 y cualquier compilador que soporte C11, lo tendrá.error: expected declaration specifiers or '...' before 'sizeof'
líneastatic_assert( sizeof(int) == sizeof(long int), "Error!);
(estoy usando C, no C ++ por cierto)_Static_assert( sizeof(int) == sizeof(long int), "Error!");
En mi macine aparece el error.error: expected declaration specifiers or '...' before 'sizeof'
Yerror: expected declaration specifiers or '...' before string constant
(se refiere a la"Error!"
cadena) (también: estoy compilando con -std = c11. Al poner la declaración dentro de una función, todo funciona bien (falla y tiene éxito como se esperaba))_Static_assert
no el C ++ ishstatic_assert
. Necesita `#include <assert.h> para obtener la macro static_assert.Esto funciona en el ámbito de función y no función (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); }
Si la aserción de tiempo de compilación no puede coincidir, GCC genera un mensaje casi inteligible
sas.c:4: error: size of array ‘static_assertion_this_should_be_true’ is negative
La macro podría o debería cambiarse para generar un nombre único para el typedef (es decir, concatenar
__LINE__
al final delstatic_assert_...
nombre)En lugar de un ternario, esto también podría usarse,
#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[2*(!!(COND))-1]
lo que funciona incluso en el compilador oxidado viejo cc65 (para la cpu 6502).ACTUALIZACIÓN: En aras de la integridad, 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 función "error" y "advertencia". Si una llamada a una función con ese atributo no se pudo eliminar mediante la eliminación del código muerto (u otras medidas), se genera un error o advertencia. Esto se puede utilizar para hacer afirmaciones en tiempo de compilación con 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
fuente
-Og
Sin embargo, el nivel mínimo de optimización ( ) puede ser suficiente para que esto funcione y no debería interferir con la depuración. Se puede considerar hacer que la aserción estática sea una aserción sin operación o en tiempo de ejecución si__OPTIMIZE__
(y__GNUC__
) no está definido.__LINE__
versión en gcc 4.1.1 ... ¡con molestias ocasionales cuando dos encabezados diferentes tienen uno en la misma línea numerada!cl
Sé que la pregunta menciona explícitamente gcc, pero solo para completar, aquí hay un ajuste para los compiladores de Microsoft.
El uso de la matriz typedef de tamaño negativo no persuade a cl a escupir un error decente. Solo dice
error C2118: negative subscript
. Un campo de bits de ancho cero se comporta mejor a este respecto. Dado que esto implica la eliminación de tipos de una estructura, realmente necesitamos usar nombres de tipos únicos.__LINE__
no corta la mostaza - es posible tener unCOMPILE_TIME_ASSERT()
en la misma línea en un encabezado y un archivo fuente, y su compilació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)
debajo
cl
da:Gcc también da un mensaje inteligible:
fuente
De Wikipedia :
#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;} COMPILE_TIME_ASSERT( BOOLEAN CONDITION );
fuente
Yo no recomendaría el uso de la solución con un
typedef
:#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1]
typedef
NO se garantiza que la declaración de matriz con la palabra clave sea evaluada en tiempo de compilación. Por ejemplo, se compilará el siguiente código en el alcance del bloque:int invalid_value = 0; STATIC_ASSERT(invalid_value, this_should_fail_at_compile_time_but_will_not);
Recomendaría esto en su lugar (en C99):
#define STATIC_ASSERT(COND,MSG) static int static_assertion_##MSG[(COND)?1:-1]
Debido a la
static
palabra clave, la matriz se definirá en tiempo de compilación. Tenga en cuenta que esta aserción solo funcionará con losCOND
que se evalúen en tiempo de compilación. No funcionará (es decir, la compilación fallará) con condiciones basadas en valores en la memoria, como los valores asignados a las variables.fuente
Si usa la macro STATIC_ASSERT () con
__LINE__
, es posible evitar conflictos de números de línea entre una entrada en un archivo .cy una entrada diferente en un archivo de encabezado al incluir__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]
fuente
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 un tamaño 1 y es válida, pero si es falsa, el tamaño de -1 da un error de compilación.
La mayoría de los compiladores mostrarán el nombre de la variable y señalarán la parte derecha del código donde puede dejar comentarios eventuales sobre la aserción.
fuente
#define STATIC_ASSERT()
macro de tipo genérico y proporcionar ejemplos más genéricos y la salida del compilador de muestra a partir de sus ejemplos genéricosSTATIC_ASSERT()
le daría muchos más votos positivos y haría que esta técnica tuviera más sentido, creo.De Perl, específicamente la
perl.h
línea 3455 (<assert.h>
se incluye de antemano):/* STATIC_ASSERT_DECL/STATIC_ASSERT_STMT are like assert(), but for compile time invariants. That is, their argument must be a constant expression that can be verified by the compiler. This expression can contain anything that's known to the compiler, e.g. #define constants, enums, or sizeof (...). If the expression evaluates to 0, compilation fails. Because they generate no runtime code (i.e. their use is "free"), they're always active, even under non-DEBUGGING builds. STATIC_ASSERT_DECL expands to a declaration and is suitable for use at file scope (outside of any function). STATIC_ASSERT_STMT expands to a statement and is suitable for use inside a function. */ #if (defined(static_assert) || (defined(__cplusplus) && __cplusplus >= 201103L)) && (!defined(__IBMC__) || __IBMC__ >= 1210) /* static_assert is a macro defined in <assert.h> in C11 or a compiler builtin in C++11. But IBM XL C V11 does not support _Static_assert, no matter what <assert.h> says. */ # define STATIC_ASSERT_DECL(COND) static_assert(COND, #COND) #else /* We use a bit-field instead of an array because gcc accepts 'typedef char x[n]' where n is not a compile-time constant. We want to enforce constantness. */ # define STATIC_ASSERT_2(COND, SUFFIX) \ typedef struct { \ unsigned int _static_assertion_failed_##SUFFIX : (COND) ? 1 : -1; \ } _static_assertion_failed_##SUFFIX PERL_UNUSED_DECL # define STATIC_ASSERT_1(COND, SUFFIX) STATIC_ASSERT_2(COND, SUFFIX) # define STATIC_ASSERT_DECL(COND) STATIC_ASSERT_1(COND, __LINE__) #endif /* We need this wrapper even in C11 because 'case X: static_assert(...);' is an error (static_assert is a declaration, and only statements can have labels). */ #define STATIC_ASSERT_STMT(COND) STMT_START { STATIC_ASSERT_DECL(COND); } STMT_END
Si
static_assert
está disponible (desde<assert.h>
), se utiliza. De lo contrario, si la condición es falsa, se declara un campo de bits con un tamaño negativo, lo que hace que la compilación falle.STMT_START
/STMT_END
son macros que se expanden ado
/while (0)
, respectivamente.fuente
Porque:
_Static_assert()
ahora está definido en gcc para todas las versiones de C, ystatic_assert()
está definido en C ++ 11 y posterioresLa siguiente macro simple para,
STATIC_ASSERT()
por lo tanto, funciona en:g++ -std=c++11
) o posteriorgcc -std=c90
gcc -std=c99
gcc -std=c11
gcc
(sin std especificado)Defina
STATIC_ASSERT
como sigue:/* For C++: */ #ifdef __cplusplus #ifndef _Static_assert #define _Static_assert static_assert /* `static_assert` is part of C++11 or later */ #endif #endif /* Now for gcc (C) (and C++, given the define above): */ #define STATIC_ASSERT(test_for_true) _Static_assert((test_for_true), "(" #test_for_true ") failed")
Ahora úsalo:
STATIC_ASSERT(1 > 2); // Output will look like: error: static assertion failed: "(1 > 2) failed"
Ejemplos:
Probado en Ubuntu usando gcc 4.8.4:
Ejemplo 1: buen
gcc
resultado (es decir, losSTATIC_ASSERT()
códigos funcionan, pero la condición era falsa, lo que provocó una afirmación en tiempo de compilación):Ejemplo 2: buen
g++ -std=c++11
resultado (es decir, losSTATIC_ASSERT()
códigos funcionan, pero la condición era falsa, lo que provocó una afirmación en tiempo de compilación):Ejemplo 3: salida de C ++ fallida (es decir, el código de aserción no funciona correctamente, ya que se usa una versión de C ++ anterior a C ++ 11):
Resultados completos de la prueba aquí:
/* static_assert.c - test static asserts in C and C++ using gcc compiler Gabriel Staples 4 Mar. 2019 To be posted in: 1. /programming/987684/does-gcc-have-a-built-in-compile-time-assert/987756#987756 2. /programming/3385515/static-assert-in-c/7287341#7287341 To compile & run: C: gcc -Wall -o static_assert static_assert.c && ./static_assert gcc -Wall -std=c90 -o static_assert static_assert.c && ./static_assert gcc -Wall -std=c99 -o static_assert static_assert.c && ./static_assert gcc -Wall -std=c11 -o static_assert static_assert.c && ./static_assert C++: g++ -Wall -o static_assert static_assert.c && ./static_assert g++ -Wall -std=c++98 -o static_assert static_assert.c && ./static_assert g++ -Wall -std=c++03 -o static_assert static_assert.c && ./static_assert g++ -Wall -std=c++11 -o static_assert static_assert.c && ./static_assert ------------- TEST RESULTS: ------------- 1. `_Static_assert(false, "1. that was false");` works in: C: gcc -Wall -o static_assert static_assert.c && ./static_assert YES gcc -Wall -std=c90 -o static_assert static_assert.c && ./static_assert YES gcc -Wall -std=c99 -o static_assert static_assert.c && ./static_assert YES gcc -Wall -std=c11 -o static_assert static_assert.c && ./static_assert YES C++: g++ -Wall -o static_assert static_assert.c && ./static_assert NO g++ -Wall -std=c++98 -o static_assert static_assert.c && ./static_assert NO g++ -Wall -std=c++03 -o static_assert static_assert.c && ./static_assert NO g++ -Wall -std=c++11 -o static_assert static_assert.c && ./static_assert NO 2. `static_assert(false, "2. that was false");` works in: C: gcc -Wall -o static_assert static_assert.c && ./static_assert NO gcc -Wall -std=c90 -o static_assert static_assert.c && ./static_assert NO gcc -Wall -std=c99 -o static_assert static_assert.c && ./static_assert NO gcc -Wall -std=c11 -o static_assert static_assert.c && ./static_assert NO C++: g++ -Wall -o static_assert static_assert.c && ./static_assert NO g++ -Wall -std=c++98 -o static_assert static_assert.c && ./static_assert NO g++ -Wall -std=c++03 -o static_assert static_assert.c && ./static_assert NO g++ -Wall -std=c++11 -o static_assert static_assert.c && ./static_assert YES 3. `STATIC_ASSERT(1 > 2);` works in: C: gcc -Wall -o static_assert static_assert.c && ./static_assert YES gcc -Wall -std=c90 -o static_assert static_assert.c && ./static_assert YES gcc -Wall -std=c99 -o static_assert static_assert.c && ./static_assert YES gcc -Wall -std=c11 -o static_assert static_assert.c && ./static_assert YES C++: g++ -Wall -o static_assert static_assert.c && ./static_assert NO g++ -Wall -std=c++98 -o static_assert static_assert.c && ./static_assert NO g++ -Wall -std=c++03 -o static_assert static_assert.c && ./static_assert NO g++ -Wall -std=c++11 -o static_assert static_assert.c && ./static_assert YES */ #include <stdio.h> #include <stdbool.h> /* For C++: */ #ifdef __cplusplus #ifndef _Static_assert #define _Static_assert static_assert /* `static_assert` is part of C++11 or later */ #endif #endif /* Now for gcc (C) (and C++, given the define above): */ #define STATIC_ASSERT(test_for_true) _Static_assert((test_for_true), "(" #test_for_true ") failed") int main(void) { printf("Hello World\n"); /*_Static_assert(false, "1. that was false");*/ /*static_assert(false, "2. that was false");*/ STATIC_ASSERT(1 > 2); return 0; }
Relacionado:
fuente
static_assert
macroassert.h
?static_assert()
no está disponible en absoluto en C. Vea aquí también: en.cppreference.com/w/cpp/language/static_assert - muestra questatic_assert
existe "(desde C ++ 11)". La belleza de mi respuesta es que funciona en gcc's C90 y posteriores, así como en cualquier C ++ 11 y posteriores, en lugar de solo en C ++ 11 y posteriores, comostatic_assert()
. Además, ¿qué tiene de complicado mi respuesta? Son solo un par de#define
s.static_assert
se define en C desde C11. Es una macro que se expande a_Static_assert
. en.cppreference.com/w/c/error/static_assert . Además, el contraste con su respuesta_Static_assert
no está disponible en c99 y c90 en gcc (solo en gnu99 y gnu90). Esto cumple con el estándar. Básicamente, hace mucho trabajo adicional, que solo aporta beneficios si se compila con gnu90 y gnu99 y que hace que el uso real sea insignificantemente pequeño.Para aquellos de ustedes que desean algo realmente básico y portátil pero no tienen acceso a las funciones de C ++ 11, escribí exactamente lo que necesita.
Use
STATIC_ASSERT
normalmente (puede escribirlo dos veces en la misma función si lo desea) y useGLOBAL_STATIC_ASSERT
fuera de 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 aserción real, que definitivamente querrá usar si está disponible.
Si no lo hace, lo afirma obteniendo su
pred
icate y dividiéndolo 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 en
1
. Entonces, si la afirmación pasa, no querrá que falle de todos modos porque su predicado evaluado como-1
(inválido), o sea232442
(desperdicio masivo de espacio, IDK si se optimizara).Porque
STATIC_ASSERT
está envuelto entre llaves, esto lo convierte en un bloque, que alcanza la variableassert
, lo que significa que puedes escribirlo muchas veces.También lo lanza a
void
, que es una forma conocida de deshacerse de lasunused variable
advertencias.Porque
GLOBAL_STATIC_ASSERT
, en lugar de estar en un bloque de código, genera un espacio de nombres. Los espacios de nombres están permitidos fuera de las funciones. Seunique
requiere un identificador para detener cualquier definición en conflicto si usa esta más de una vez.Trabajó para mí en GCC y VS'12 C ++
fuente
Esto funciona con la opción "eliminar no utilizados" configurada. 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 //
fuente
Esto funcionó para algunos viejos gcc. Lamento 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
fuente