¿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?
¿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?
Respuestas:
El estándar C11 agrega la _Static_assert
palabra clave.
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 un literal de cadena constante que puede ser long ( _Static_assert(0, L"assertion of doom!")
).
Debo señalar que esto también se implementa en versiones recientes de clang.
error: expected declaration specifiers or '...' before 'sizeof'
línea static_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'
Y error: 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 ++ ish static_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 del static_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
-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!
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 un COMPILE_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:
error C2149: 'static_assertion_failed_use_another_compiler_luke': el campo de bit con nombre no puede tener un ancho cero
Gcc también da un mensaje inteligible:
error: ancho cero para el campo de bits 'static_assertion_failed_use_another_compiler_luke'
De Wikipedia :
#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;}
COMPILE_TIME_ASSERT( BOOLEAN CONDITION );
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 los COND
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.
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]
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.
#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éricos STATIC_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 a do
/ while (0)
, respectivamente.
_Static_assert()
ahora está definido en gcc para todas las versiones de C, y static_assert()
está definido en C ++ 11 y posterioresSTATIC_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"
Probado en Ubuntu usando gcc 4.8.4:
Ejemplo 1: buen gcc
resultado (es decir, los STATIC_ASSERT()
códigos funcionan, pero la condición era falsa, lo que provocó una afirmación en tiempo de compilación):
$ gcc -Wall -o static_assert static_assert.c && ./static_assert
static_assert.c: En la función 'main'
static_assert.c: 78: 38: error: la aserción estática falló: "(1> 2) falló"
#define STATIC_ASSERT (test_for_true ) _Static_assert ((prueba_para_verdadero), "(" #prueba_para_verdadero ") falló")
^
static_assert.c: 88: 5: nota: en expansión de macro 'STATIC_ASSERT'
STATIC_ASSERT (1> 2);
^
Ejemplo 2: buen g++ -std=c++11
resultado (es decir, los STATIC_ASSERT()
códigos funcionan, pero la condición era falsa, lo que provocó una afirmación en tiempo de compilación):
$ g ++ -Wall -std = c ++ 11 -o static_assert static_assert.c && ./static_assert
static_assert.c: En la función 'int main ()'
static_assert.c: 74: 32: error: la aserción estática falló: (1> 2) falló
#define _Static_assert static_assert / *static_assert
es parte de C ++ 11 o posterior * /
^
static_assert.c: 78: 38: nota: en la expansión de la macro '_Static_assert'
#define STATIC_ASSERT (test_for_true) _Static_assert ((test_for_true), "(" #test_for_true ") falló")
^
static_assert.c: 88: 5: nota: en la expansión de la macro 'STATIC_ASSERT'
STATIC_ASSERT (1> 2);
^
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):
$ g ++ -Wall -o static_assert static_assert.c && ./static_assert
static_assert.c: 88: 5: advertencia: el identificador 'static_assert' es una palabra clave en C ++ 11 [-Wc ++ 0x-compat]
STATIC_ASSERT (1> 2 );
^
static_assert.c: En la función 'int main ()'
static_assert.c: 78: 99: error: 'static_assert' no se declaró en este ámbito
#define STATIC_ASSERT (test_for_true) _Static_assert ((test_for_true), "(" #test_for_true " ) falló ")
^
static_assert.c: 88: 5: nota: en expansión de la macro 'STATIC_ASSERT'
STATIC_ASSERT (1> 2);
^
/*
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;
}
static_assert
macro assert.h
?
static_assert()
no está disponible en absoluto en C. Vea aquí también: en.cppreference.com/w/cpp/language/static_assert - muestra que static_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, como static_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 use GLOBAL_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 sea 232442
(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 las unused 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. Se unique
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 ++
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
//
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
_Static_assert
es parte del estándar C11 y cualquier compilador que soporte C11, lo tendrá.