Qué es ": -!!"en código C?


Me encontré 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é hace :-!!?

Author: mpen, 2012-02-10

6 answers

Esta es, en efecto, una forma de comprobar si la expresión e puede ser evaluada como 0, y si no, para fallar la compilación.

La macro está algo mal nombrada; 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); }))
  1. (e): Calcular la expresión e.

  2. !!(e): Lógicamente niega dos veces: 0 si e == 0; de lo contrario 1.

  3. -!!(e): Numéricamente negar la expresión del paso 2: 0 si era 0; de lo contrario -1.

  4. struct{int: -!!(0);} --> struct{int: 0;}: Si era cero, entonces declaramos una estructura con un entero anónimo bitfield que tiene anchura cero. Todo está bien y procedemos con normalidad.

  5. struct{int: -!!(1);} --> struct{int: -1;}: Por otro lado, si no cero, entonces será algún número negativo. Declarando cualquier bitfield con negative width es un error de compilación.

Así que vamos a terminar con un bitfield que tiene ancho 0 en una estructura, que está bien, o un bitfield con ancho negativo, que es un error de compilación. Luego tomamos sizeof ese campo, por lo que obtenemos un size_t con el ancho apropiado (que será cero en el caso donde e es cero).


Algunas personas han preguntado: ¿Por qué no usar un assert?

La respuesta de Keithmo aquí tiene un buena respuesta:

Estas macros implementan una prueba en tiempo de compilación, mientras que assert() es una prueba en tiempo de ejecución.

Exactamente correcto. ¡No desea 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 que se puedan detectar problemas en tiempo de compilación, tanto mejor.

 1550
Author: John Feminella,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:55:00

El : es un campo de bits. En cuanto a !!, eso es doble negación lógica y así 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 conseguir que el compilador barf en entradas no válidas.

Considere BUILD_BUG_ON_ZERO. Cuando -!!(e) se evalúa a un valor negativo, se produce un error de compilación. De lo contrario -!!(e) se 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 la compilación de hecho falla cuando la entrada es no cero.

BUILD_BUG_ON_NULL es muy similar, pero produce un puntero en lugar de un int.

 240
Author: David Heffernan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:45

Algunas personas parecen estar confundiendo estas macros con assert().

Estas macros implementan una prueba en tiempo de compilación, mientras que assert() es una prueba en tiempo de ejecución.

 151
Author: keithmo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-08 22:22:41

Bueno, estoy bastante sorprendido de que las alternativas a esta sintaxis no se hayan mencionado. Otro mecanismo común (pero 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.

#define MY_COMPILETIME_ASSERT(test)              \
    do {                                         \
        extern void you_did_something_bad(void); \
        if (!(test))                             \
            you_did_something_bad(void);         \
    } while (0)

Mientras este mecanismo funciona (siempre y cuando las optimizaciones estén habilitadas) tiene la desventaja de no reportar un error hasta que se enlaza, momento en el que no encuentra la definición para la función you_did_something_bad(). Es por eso que los desarrolladores del kernel comienzan a usar trucos como los anchos de campo de bits de tamaño negativo y los arrays de tamaño negativo (el último de los cuales dejó de romper compilaciones en GCC 4.4).

En simpatía por la necesidad de aserciones en tiempo de compilación, GCC 4.3 introdujo el error atributo de función que le permite extender este concepto más antiguo, pero generar un error en tiempo de compilación con un mensaje de su elección no no más error críptico de "matriz de tamaño negativo" mensajes!

#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 característica y la mayoría de las macros en bug.h se han actualizado en consecuencia. Aún así, esta macro no se puede usar como inicializador. Sin embargo, utilizando por expresiones de declaración (otra GCC C-extensión), usted puede!

#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 en tiempo de compilación que diga " I ¡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 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 su uno mismo (es decir, se puede evaluar completamente en tiempo de compilación y de lo contrario pasa el __builtin_constant_p() test). Además, no se pueden usar fuera de un cuerpo de función.

Con suerte, GCC corregirá estas deficiencias pronto y permitirá que las expresiones de declaración constante se utilicen como inicializadores constantes. El desafío aquí es la especificación del lenguaje que define lo que es una expresión constante legal. C++11 agregó la palabra clave constexpr solo para este tipo o cosa, pero no existe contraparte en C11. Mientras que C11 obtuvo aserciones estáticas, que resolverán parte de este problema, no resolverá todas estas deficiencias. Así que espero que gcc pueda hacer una funcionalidad constexpr disponible como una extensión a través de-std = gnuc99 & - std=gnuc11 o algo así y permitir su uso en expresiones de instrucción et. al.

 42
Author: Daniel Santos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-17 22:08:28

Está creando un campo de bits size 0 si la condición es false, pero un tamaño -1 (-!!1) bitfield si la condición es true / non-zero. En el primer caso, no hay error y la estructura se inicializa con un miembro int. En este último caso, hay un error de compilación (y no se crea un campo de bits de tamaño -1, por supuesto).

 31
Author: Matt Phillips,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-10 14:56:35
 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); }))
 -4
Author: leesagacious,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-24 11:07:40