O que é ":-!!" em código C?

Eu esbarrei neste estranho código macro em /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); }))

O que faz o :-!!?

Solução

Esta é, com efeito, **uma forma de verificar se a expressão e pode ser avaliada como 0, e se não, para falhar a construção***.

A macro é um pouco mal chamada; deveria ser algo mais como BUILD_BUG_OR_ZERO, em vez de ...ON_ZERO. (Tem havido discussões ocasionais sobre se este é um nome confuso.)

Devias ler a expressão desta maneira:

sizeof(struct { int: -!!(e); }))
  1. (e): Expressão computativa e.

  2. !!(e): Logicamente negar duas vezes: 0 se e == 0; caso contrário 1.

  3. -!!(e): Nega numericamente a expressão do passo 2: 0 se fosse 0; caso contrário -1.

  4. struct{int: -!!(0);} --> struct{int: 0;}: Se era zero, então declaramos uma estrutura com um campo de bits inteiro anónimo que tem largura zero. Tudo está bem e procedemos normalmente.

  5. struct{int: -!!(1);} --> struct{int: -1;}: Por outro lado, se não for não for zero, então será um número negativo. Declarar qualquer bitfield com negative width é um erro de compilação.

Então vamos acabar com um bitfield que tem largura 0 em uma estrutura, o que é bom, ou um bitfield com largura negativa, que é um erro de compilação. Então pegamos sizeof nesse campo, então obtemos um size_t com a largura apropriada (que será zero no caso em que e for zero).


Algumas pessoas têm perguntado: **"Por que não usar apenas um "assert"?

resposta de keithmo aqui tem uma boa resposta:

Estas macros implementam um teste em tempo de compilação, enquanto assert() é um teste em tempo de execução.

Exatamente certo. Você não quer detectar problemas no seu kernel em tempo de execução que poderiam ter sido detectados mais cedo! É uma peça crítica do sistema operacional. Seja qual for a extensão dos problemas que possam ser detectados em tempo de compilação, tanto melhor.

Comentários (29)

O : é um bitfield. Quanto a !!, isto é dupla negação lógica e assim retorna 0 para falso ou 1 para verdadeiro. E o - é um sinal de menos, ou seja, negação aritmética.

É tudo apenas um truque para levar o compilador a vomitar em entradas inválidas.

Considere BUILD_BUG_ON_ZERO. Quando -!!(e) avalia para um valor negativo, isso produz um erro de compilação. Caso contrário o -!!(e) avalia para 0, e um bitfield de largura 0 tem tamanho 0. E por isso a macro avalia para um size_t com valor 0.

O nome é fraco no meu ponto de vista porque a construção de fato falha quando a entrada é não zero.

BUILD_BUG_ON_NULLé muito semelhante, mas produz um ponteiro em vez de umint`.

Comentários (11)

Está criando um bitfield de tamanho 0 se a condição for falsa, mas um bitfield de tamanho -1 (-!!1) se a condição for verdadeira/não-zero. No primeiro caso, não há erro e a estrutura é inicializada com um membro int. No segundo caso, há um erro de compilação (e não é criado um bitfield de tamanho -1, é claro).

Comentários (1)