Qué es ‘:-!!’ ¿Cª?

10 minutos de lectura

avatar de usuario de chmurli
chmurli

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

  • – Unario menos
    ! NOT lógico
    inverso not not del entero e dado, por lo que la variable puede ser 0 o 1.

    – CyrillC

    10 de febrero de 2012 a las 14:58

  • git Blau nos dice que esta forma particular de aserción estática fue introducido por Jan Beulich en 8c87df4. Obviamente, tenía buenas razones para hacerlo (ver el mensaje de confirmación).

    – Niklas B.

    10 de febrero de 2012 a las 16:34


  • Casi no hace falta decir que el campo de bits creado es anónimo. Esto tiene el mismo espíritu que la metaprogramación de plantillas de C++, es decir, suceden cosas en tiempo de compilación que se pueden verificar en tiempo de compilación.

    – forgan1

    16 de febrero de 2012 a las 3:05

  • Espera, pensé que los argumentos de sizeof no se evalúan. ¿Está mal en este caso? Si es así, ¿por qué? ¿Porque es una macro?

    – Felipe Nube

    11 de marzo de 2012 a las 15:49

  • @cpcloud, sizeof “evalúa” el tipo, pero no el valor. Es el tipo que no es válido en este caso.

    –Winston Ewert

    24 de marzo de 2012 a las 17:19

Avatar de usuario de John Feminilla
Juan Feminella

Esto es, en efecto, una forma de verificar si la expresión e puede evaluarse como 0 y, de no ser así, fallar la compilación.

La macro tiene un nombre algo incorrecto; debería ser algo más como BUILD_BUG_OR_ZEROen vez 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 expresión e.

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

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

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

  5. 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 negativo ancho es un error de compilación.

Entonces terminaremos con un campo de bits que tiene un ancho de 0 en una estructura, lo cual está bien, o un campo de bits con un ancho negativo, que es un error de compilación. entonces tomamos sizeof ese campo, por lo que obtenemos un size_t con el ancho adecuado (que será cero en el caso de que e es cero).


Algunas personas han preguntado: ¿Por qué no usar simplemente 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 desea detectar problemas en su núcleo en tiempo de ejecución que podría haber sido capturado 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.

  • las variantes recientes de los estándares C++ o C tienen algo como static_assert para fines relacionados.

    – Basile Starynkevitch

    10/02/2012 a las 17:00

  • @Lundin: #error requeriría el uso de 3 líneas de código #if/#error/#endif, y solo funcionaría para evaluaciones accesibles para el preprocesador. Este truco funciona para cualquier evaluación accesible al compilador.

    –Ed Staub

    10 de febrero de 2012 a las 17:50

  • El kernel de Linux no usa C++, al menos no mientras Linus siga vivo.

    – Mark Ransom

    10 de febrero de 2012 a las 17:52

  • @Dolda2000: “Las expresiones booleanas en C se definen para evaluar siempre a cero o uno” — No exactamente. El operadores que producen resultados “lógicamente booleanos” (!, <, >, <=, >=, ==, !=, &&, ||) siempre dan 0 o 1. Otras expresiones pueden dar resultados que pueden usarse como condiciones, pero son simplemente cero o distintos de cero; Por ejemplo, isdigit(c)dónde c es un dígito, puede producir cualquier valor distinto de cero (que luego se trata como verdadero en una condición).

    –Keith Thompson

    3 de abril de 2013 a las 3:23

  • Nota rápida sobre el nombre. Se llama ...ON_ZERO porque es un derivado de BUG_ONuna macro que es esencialmente una afirmación. BUG_ON(foo) significa “es un error si foo es verdadero” (en tiempo de ejecución). Por el contrario, BUILD_BUG_ON es una afirmación estática (comprobada en el momento de la compilación), y finalmente BUILD_BUG_ON_ZERO es exactamente lo mismo, excepto que todo es una expresión igual a (size_t)0como dice el comentario en la pregunta.

    – Xión

    21 de noviembre de 2016 a las 23:37

Avatar de usuario de David Heffernan
David Heffernan

El : es un campo de bits. Como para !!eso es doble negación lógica y por lo tanto devuelve 0 por falso o 1 de verdad. Y el - es un signo menos, es decir, una negación aritmética.

Todo es solo un truco para hacer que el compilador vomite en entradas no válidas.

Considerar BUILD_BUG_ON_ZERO. Cuando -!!(e) evalúa a un valor negativo, que produce un error de compilación. De lo contrario -!!(e) se evalúa como 0, y un campo de bits de ancho 0 tiene un tamaño de 0. Y, por lo tanto, la macro se evalúa como un size_t con valor 0.

En mi opinión, el nombre es débil porque, de hecho, la compilación falla cuando la entrada es no cero.

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

  • es sizeof(struct { int:0; }) estrictamente conforme?

    – ouah

    10 de febrero de 2012 a las 15:08

  • ¿Por qué el resultado en general sería 0? A struct con solo un campo de bits vacío, es cierto, pero no creo que se permitan estructuras con tamaño 0. Por ejemplo, si creara una matriz de ese tipo, los elementos individuales de la matriz aún deben tener direcciones diferentes, ¿no?

    – Jens Gusted

    10 de febrero de 2012 a las 15:09

  • en realidad no les importa, ya que usan extensiones GNU, deshabilitan la regla de alias estricta y no consideran los desbordamientos de enteros como UB. Pero me preguntaba si esto se ajusta estrictamente a C.

    – ouah

    10 de febrero de 2012 a las 15:13

  • @ouah con respecto a los campos de bits de longitud cero sin nombre, consulte aquí: stackoverflow.com/questions/4297095/…

    –David Heffernan

    10 de febrero de 2012 a las 15:14


  • @DavidHeffernan en realidad C permite un campo de bits sin nombre de 0 ancho, pero no si no hay otro miembro con nombre en la estructura. (C99, 6.7.2.1p2) "If the struct-declaration-list contains no named members, the behavior is undefined." Así por ejemplo sizeof (struct {int a:1; int:0;}) es estrictamente conforme pero sizeof(struct { int:0; }) no es (comportamiento indefinido).

    – ouah

    16/10/2012 a las 13:51

avatar de usuario de keithmo
keithmo

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 de tiempo de ejecución.

Avatar de usuario de Daniel Santos
Daniel Santos

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.

#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 vincula, momento en el que no puede encontrar la definición de la función you_hizo_algo_malo(). Es por eso que los desarrolladores del kernel comenzaron a usar trucos como los anchos de campo de bits de tamaño negativo y las matrices 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 eso le permite ampliar este concepto anterior, pero generar un error de 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 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, usando por expresiones de declaración (otra extensión GCC C), ¡tú 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 dice “¡Te dije que no me dieras un cinco!” si la expresión se evalúa como cinco o no es una constante de tiempo de compilación.

Entonces, ¿por qué no usamos esto en lugar de campos de bits de tamaño negativo? Por desgracia, actualmente existen 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 declaración es completamente constante en sí misma (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 utilizar fuera del cuerpo de una función.

Con suerte, GCC corregirá estas deficiencias pronto y permitirá que las expresiones de declaraciones constantes se usen 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 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 permita su uso en expresiones de declaración et. Alabama.

Avatar de usuario de Matt Phillips
mate phillips

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

  • En realidad está devolviendo un size_t con valor 0 en caso de que la condición sea verdadera.

    –David Heffernan

    10 de febrero de 2012 a las 14:58

  • En realidad está devolviendo un size_t con valor 0 en caso de que la condición sea verdadera.

    –David Heffernan

    10 de febrero de 2012 a las 14:58

¿Ha sido útil esta solución?