Me topé con este extraño código macro en /usr/include/linux/kernel.h :
/* Force a compilation error if condition is true, but also produce a
result (of value 0 and type size_t), so the expression can be used
e.g. in a structure initializer (or where-ever else comma expressions
aren't permitted). */
#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))
¿Qué :-!!
hacer?
c
linux
macros
linux-kernel
chmurli
fuente
fuente
sizeof
"evalúa" el tipo, pero no el valor. Es el tipo que no es válido en este caso.Respuestas:
Esta es, en efecto, una forma de verificar si la expresión e puede evaluarse como 0, y si no, fallar la compilación .
La macro está mal nombrada; debería ser algo más parecido
BUILD_BUG_OR_ZERO
, en lugar de...ON_ZERO
. (Ha habido discusiones ocasionales sobre si este es un nombre confuso ).Deberías leer la expresión así:
(e)
: Calcular expresióne
.!!(e)
: Negar lógicamente dos veces:0
sie == 0
; de otro modo1
.-!!(e)
: Numéricamente niega la expresión del paso 2:0
si lo fue0
; de otro modo-1
.struct{int: -!!(0);} --> struct{int: 0;}
: Si fue cero, entonces declaramos una estructura con un campo de bits entero anónimo que tiene un ancho cero. Todo está bien y procedemos normalmente.struct{int: -!!(1);} --> struct{int: -1;}
: Por otro lado, si no es cero, entonces será un número negativo. Declarar cualquier campo de bits con ancho negativo es un error de compilación.Así que terminaremos con un campo de bits que tiene un ancho 0 en una estructura, que está bien, o un campo de bits con ancho negativo, que es un error de compilación. Luego tomamos
sizeof
ese campo, por lo que obtenemos unsize_t
con el ancho apropiado (que será cero en el caso dondee
sea cero).Algunas personas han preguntado: ¿por qué no solo usar un
assert
?La respuesta de Keithmo aquí tiene una buena respuesta:
Exactamente correcto. ¡No querrá detectar problemas en su núcleo en tiempo de ejecución que podrían haberse detectado antes! Es una pieza crítica del sistema operativo. En cualquier medida, los problemas se pueden detectar en tiempo de compilación, tanto mejor.
fuente
static_assert
para fines relacionados.!
,<
,>
,<=
,>=
,==
,!=
,&&
,||
) siempre producen 0 o 1. Otras expresiones pueden producir resultados que pueden ser utilizados como una condiciones, pero son meramente cero o distinto de cero; por ejemplo,isdigit(c)
dondec
es un dígito, puede producir cualquier valor distinto de cero (que luego se trata como verdadero en una condición).El
:
es un campo de bits. En cuanto a!!
, esa es la doble negación lógica y, por lo tanto, devuelve0
falso o1
verdadero. Y el-
es un signo menos, es decir, negación aritmética.Todo es solo un truco para que el compilador vomite entradas no válidas.
Considere
BUILD_BUG_ON_ZERO
. Cuando se-!!(e)
evalúa a un valor negativo, eso produce un error de compilación. De lo contrario, se-!!(e)
evalúa a 0, y un campo de bits de ancho 0 tiene un tamaño de 0. Y, por lo tanto, la macro se evalúa a asize_t
con valor 0.El nombre es débil en mi opinión porque la construcción de hecho falla cuando la entrada no es cero.
BUILD_BUG_ON_NULL
es muy similar, pero produce un puntero en lugar de unint
.fuente
sizeof(struct { int:0; })
estrictamente conforme?0
? Astruct
con solo un campo de bits vacío, cierto, pero no creo que se permitan estructuras con tamaño 0. Por ejemplo, si crea una matriz de ese tipo, los elementos individuales de la matriz aún deben tener direcciones diferentes, ¿no?0
ancho de bit sin nombre , pero no si no hay otro miembro nombrado en la estructura.(C99, 6.7.2.1p2) "If the struct-declaration-list contains no named members, the behavior is undefined."
Entonces, por ejemplo,sizeof (struct {int a:1; int:0;})
es estrictamente conforme perosizeof(struct { int:0; })
no lo es (comportamiento indefinido).Algunas personas parecen estar confundiendo estas macros con
assert()
.Estas macros implementan una prueba de tiempo de compilación, mientras que
assert()
es una prueba de tiempo de ejecución.fuente
Bueno, estoy bastante sorprendido de que no se hayan mencionado las alternativas a esta sintaxis. Otro mecanismo común (pero más antiguo) es llamar a una función que no está definida y confiar en el optimizador para compilar la llamada a la función si su afirmación es correcta.
Si bien este mecanismo funciona (siempre y cuando las optimizaciones estén habilitadas) tiene el inconveniente de no informar un error hasta que se enlace, momento en el cual no puede encontrar la definición de la función you_did_something_bad (). Es por eso que los desarrolladores de kernel comienzan a usar trucos como los anchos de campo de bits de tamaño negativo y las matrices de tamaño negativo (la última de las cuales dejó de romper las compilaciones en GCC 4.4).
En simpatía por la necesidad de afirmaciones en tiempo de compilación, GCC 4.3 introdujo el
error
atributo de función que le permite ampliar este concepto anterior, pero generar un error en tiempo de compilación con un mensaje de su elección: no más críptico "matriz de tamaño negativo" " ¡error de mensajes!De hecho, a partir de Linux 3.9, ahora tenemos una macro llamada
compiletime_assert
que utiliza esta función y la mayoría de las macrosbug.h
se han actualizado en consecuencia. Aún así, esta macro no se puede usar como inicializador. Sin embargo, si usa expresiones de enunciado (otra extensión C de GCC), ¡puede hacerlo!Esta macro evaluará su parámetro exactamente una vez (en caso de que tenga efectos secundarios) y creará un error en tiempo de compilación que dice "¡Te dije que no me dieras cinco!" si la expresión se evalúa a cinco o no es una constante en tiempo de compilación.
Entonces, ¿por qué no estamos usando esto en lugar de campos de bits de tamaño negativo? Por desgracia, actualmente existen muchas restricciones en el uso de expresiones de enunciados, incluido su uso como inicializadores constantes (para constantes enum, ancho de campo de bits, etc.) incluso si la expresión de enunciados es completamente constante en sí misma (es decir, puede evaluarse completamente en tiempo de compilación y de lo contrario pasa la
__builtin_constant_p()
prueba). Además, no se pueden usar fuera del cuerpo de una función.Con suerte, GCC enmendará estas deficiencias pronto y permitirá que se utilicen expresiones de enunciados constantes como inicializadores constantes. El desafío aquí es la especificación del lenguaje que define lo que es una expresión legal constante. C ++ 11 agregó la palabra clave constexpr solo para este tipo o cosa, pero no existe una contraparte en C11. Si bien C11 obtuvo aserciones estáticas, que resolverán parte de este problema, no resolverá todas estas deficiencias. Así que espero que gcc pueda hacer que una funcionalidad constexpr esté disponible como una extensión a través de -std = gnuc99 & -std = gnuc11 o algo así y permitir su uso en expresiones de declaración et. Alabama.
fuente
so the expression can be used e.g. in a structure initializer (or where-ever else comma expressions aren't permitted).
" La macro devuelve una expresión de tiposize_t
error: bit-field ‘<anonymous>’ width not an integer constant
Solo permite constantes. Entonces, ¿de qué sirve?Está creando un
0
campo de bits de tamaño si la condición es falsa, pero un campo de bits de tamaño-1
(-!!1
) si la condición es verdadera / distinta de cero. En el primer caso, no hay error y la estructura se inicializa con un miembro int. En el último caso, hay un error de compilación (y-1
, por supuesto, no se crea un campo de bits de tamaño ).fuente
size_t
valor 0 en caso de que la condición sea verdadera.