proposito - ¿Qué es “:-!!” en código C?
qué es lenguaje de programación c (6)
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.
Me topé con este extraño código de 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); }))
Lo que hace :-!!
¿hacer?
Bueno, estoy bastante sorprendido de que las alternativas a esta sintaxis no hayan sido mencionadas. 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 de función si su afirmación es correcta.
#define MY_COMPILETIME_ASSERT(test) /
do { /
extern void you_did_something_bad(void); /
if (!(test)) /
you_did_something_bad(void); /
} while (0)
Si bien este mecanismo funciona (siempre que las optimizaciones estén habilitadas), tiene el inconveniente de no informar un error hasta que se vincule, momento en el que no encuentra 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 los arreglos de tamaño negativo (el último de los cuales dejó de generar compilaciones en GCC 4.4).
En simpatía por la necesidad de afirmaciones en tiempo de compilación, GCC 4.3 introdujo el atributo de función de error
que le permite ampliar este concepto anterior, pero genera un error en tiempo de compilación con un mensaje de su elección: no más críptico "tamaño negativo array "mensajes de error!
#define MAKE_SURE_THIS_IS_FIVE(number) /
do { /
extern void this_isnt_five(void) __attribute__((error( /
"I asked for five and you gave me " #number))); /
if ((number) != 5) /
this_isnt_five(); /
} while (0)
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 macros en bug.h
se han actualizado en consecuencia. Aún así, esta macro no se puede utilizar como un inicializador. Sin embargo, utilizando expresiones de declaración (otra extensión C de GCC), ¡puedes!
#define ANY_NUMBER_BUT_FIVE(number) /
({ /
typeof(number) n = (number); /
extern void this_number_is_five(void) __attribute__(( /
error("I told you not to give me a five!"))); /
if (n == 5) /
this_number_is_five(); /
n; /
})
Esta macro evaluará su parámetro exactamente una vez (en caso de que tenga efectos secundarios) y creará un error de compilación que dice "¡Te dije que no me dieras un cinco!" si la expresión se evalúa como cinco o no es una constante de compilación.
Entonces, ¿por qué no estamos usando esto en lugar de campos de bits de tamaño negativo? Por desgracia, actualmente hay muchas restricciones en el uso de expresiones de declaración, incluido su uso como inicializadores constantes (para constantes de enumeración, ancho de campo de bits, etc.) incluso si la expresión de la declaración es completamente constante en sí misma (es decir, se puede evaluar completamente) en tiempo de compilación y de otro modo pasa la prueba __builtin_constant_p()
). Además, no se pueden usar fuera del cuerpo de una función.
Con suerte, GCC enmendará estas deficiencias pronto y permitirá que las expresiones de declaración constantes se usen como inicializadores constantes. El desafío aquí es la especificación del lenguaje que define qué es una expresión constante legal. C ++ 11 agregó la palabra clave constexpr solo para este tipo o cosa, pero no existe una contraparte en C11. Si bien C11 obtuvo afirmaciones 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 similar y permita su uso en expresiones de sentencias et. Alabama.
El :
es un campo de bits. En cuanto a !!
, eso es una doble negación lógica y, por lo tanto, devuelve 0
para falso o 1
para verdadero. Y el -
es un signo menos, es decir, la negación aritmética.
Todo es solo un truco para que el compilador barf en entradas inválidas.
Considera BUILD_BUG_ON_ZERO
. Cuando -!!(e)
evalúa como un valor negativo, se produce un error de compilación. De lo contrario, -!!(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 evalúa a un size_t
con valor 0.
El nombre es débil en mi opinión porque, de hecho, la construcción falla cuando la entrada no es cero.
BUILD_BUG_ON_NULL
es muy similar, pero produce un puntero en lugar de un int
.
Está creando un campo de bits de tamaño 0
si la condición es falsa, pero un campo de bits de tamaño -1
( -!!1
) si la condición es verdadera / no 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 no se crea un campo de bits de tamaño -1
, por supuesto).
Esto es, en efecto, una manera de verificar si la expresión e puede evaluarse como 0, y si no, para fallar la compilación .
La macro es un poco mal llamada; debería ser algo más como 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í:
sizeof(struct { int: -!!(e); }))
(e)
: Expresión de cómputoe
.!!(e)
: negar lógicamente dos veces:0
sie == 0
; de lo contrario1
.-!!(e)
: negar numéricamente la expresión del paso 2:0
si era0
; de lo contrario-1
.struct{int: -!!(0);} --> struct{int: 0;}
: Si era cero, declaramos una estructura con un campo de bits de número entero anónimo que tiene ancho cero. Todo está bien y procedemos de la forma habitual.struct{int: -!!(1);} --> struct{int: -1;}
: Por otra parte, si no es cero, será un número negativo. Declarar cualquier campo de bits con ancho negativo es un error de compilación.
Entonces, o bien terminaremos con un campo de bits que tiene un ancho 0 en una estructura, que está bien, o un campo de bits con un ancho negativo, que es un error de compilación. Luego tomamos sizeof
ese campo, así obtenemos size_t
con el ancho apropiado (que será cero en el caso donde e
es cero).
Algunas personas han preguntado: ¿Por qué no usar simplemente una assert
?
La respuesta de Keithmo aquí tiene una buena respuesta:
Estas macros implementan una prueba de tiempo de compilación, mientras que assert () es una prueba de tiempo de ejecución.
Exactamente correcto. ¡No desea detectar problemas en su kernel en tiempo de ejecución que podrían haberse detectado antes! Es una pieza crítica del sistema operativo. En la medida en que se puedan detectar problemas en el momento de la compilación, tanto mejor.
Linux Kernel :
/* 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); }))