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


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); }))

Que hace :-!! ¿hacer?


1476
2018-02-10 14:50


origen


Respuestas:


Esto es, en efecto, una forma de comprobar si la expresión e se puede evaluar como 0, y si no, fallar la construcción.

La macro es algo mal llamada; debería ser algo más como BUILD_BUG_OR_ZERO, más bien que ...ON_ZERO. (Ha habido discusiones ocasionales sobre si este es un nombre confuso.)

Deberías leer la expresión de esta manera:

sizeof(struct { int: -!!(e); }))
  1. (e): Expresión de cálculo e.

  2. !!(e): Negar lógicamente dos veces: 0 Si e == 0; de otra manera 1.

  3. -!!(e): Niega numéricamente la expresión del paso 2: 0 si era 0; de otra manera -1.

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

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

Así que terminaremos con un campo de bits que tenga ancho 0 en una estructura, lo cual está bien, o un campo de bits con ancho negativo, que es un error de compilación. Entonces tomamos sizeof ese campo, entonces 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 solo usar un assert?

la respuesta de keithmo aquí tiene una 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 quiere detectar problemas en su núcleo en tiempo de ejecución que podría haber sido capturado antes! Es una pieza fundamental del sistema operativo. En cualquier medida que los problemas puedan detectarse en tiempo de compilación, tanto mejor.


1530
2018-02-10 15:04



los : es un campo de bit. Como para !!, es decir doble negación lógica y entonces vuelve 0 por falso o 1 de verdad. Y el - es un signo menos, es decir, negación aritmética.

Todo es solo un truco para que el compilador elimine las entradas inválidas.

Considerar BUILD_BUG_ON_ZERO. Cuando -!!(e) evalúa a un valor negativo, que produce un error de compilación. De otra manera -!!(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 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.


235
2018-02-10 14:54



Algunas personas parecen confundir estas macros con assert().

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


149
2018-02-10 15:37



Bien, 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 la desventaja de no informar un error hasta que se enlace, momento en el cual no se encuentra la definición de 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 ellos dejó de romper 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 extenderse sobre este concepto más antiguo, pero generar un error en tiempo de compilación con un mensaje de su elección, ¡no más mensajes de error crípticos de "matriz de tamaño negativo"!

#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 usa esta característica y la mayoría de las macros en bug.h se han actualizado en consecuencia. Aún así, esta macro no puede usarse como un inicializador. Sin embargo, usando por 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 en tiempo de compilación que diga "¡Te dije que no me dieras un cinco!" si la expresión se evalúa a cinco o no es una constante de 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 al uso de expresiones de enunciados, incluido su uso como inicializadores constantes (para constantes de enum, ancho de campo de bits, etc.) incluso si la expresión de enunciado es completamente constante (es decir, puede evaluarse completamente) en tiempo de compilación y de lo contrario pasa el __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á expresiones constantes de declaración que se utilizarán como inicializadores constantes. El desafío aquí es la especificación del lenguaje que define qué es una expresión legal constante. C ++ 11 agregó la palabra clave constexpr para este tipo o cosa, pero no existe una contraparte en C11. Si bien C11 obtuvo aserciones estáticas, lo que resolverá 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 y -std = gnuc11 o algo así y permita su uso en expresiones de declaraciones et. Alabama.


42
2018-06-27 08:21



Está creando un tamaño 0 campo de bits si la condición es falsa, pero un tamaño -1 (-!!1) campo de bits si la condición es verdadera / diferente 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 no hay tal cosa como un tamaño -1 bitfield es creado, por supuesto).


31
2018-02-10 14:54



 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); }))

-1
2018-06-21 07:18