ti-enxame.com

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

Eu esbarrei neste estranho código de 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?

1576
chmurli

Isso é, na verdade, uma maneira de verificar se a expressão e pode ser avaliada como 0, e se não, falhar na construção .

A macro é um pouco mal nomeada; deve ser algo mais parecido com BUILD_BUG_OR_ZERO, em vez de ...ON_ZERO. (Houvediscussões ocasionais sobre se este é um nome confuso.)

Você deveria ler a expressão assim:

sizeof(struct { int: -!!(e); }))
  1. (e): Compute expression e.

  2. !!(e): Logicamente negar duas vezes: 0 if e == 0; Caso contrário, 1.

  3. -!!(e): nega numericamente a expressão do passo 2: 0 se for 0; Caso contrário, -1.

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

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

Então, vamos acabar com um bitfield que tenha largura 0 em uma struct, o que é bom, ou um bitfield com largura negativa, que é um erro de compilação. Então tomamos sizeof esse campo, então obtemos um size_t com a largura apropriada (que será zero no caso onde e é zero).


Algumas pessoas perguntaram: Por que não apenas usar um assert?

resposta do keithmo aqui tem uma boa resposta:

Essas 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 em seu kernel em tempo de execução que poderiam ter sido detectados anteriormente! É uma parte crítica do sistema operacional. Até onde os problemas podem ser detectados em tempo de compilação, tanto melhor.

1611
John Feminella

O : é um campo de bits. Quanto a !!, isso é negação dupla lógica e retorna 0 para false ou 1 para true. E o - é um sinal de menos, ou seja, negação aritmética.

É tudo apenas um truque para fazer o compilador invadir entradas inválidas.

Considere BUILD_BUG_ON_ZERO. Quando -!!(e) avalia um valor negativo, isso produz um erro de compilação. Caso contrário, -!!(e) é avaliado como 0 e um campo de bits de largura 0 tem tamanho 0. E, portanto, a macro é avaliada como size_t com valor 0.

O nome é fraco na minha opinião, porque a compilação falha quando a entrada é não zero.

BUILD_BUG_ON_NULL é muito semelhante, mas produz um ponteiro em vez de uma int.

248
David Heffernan

Algumas pessoas parecem estar confundindo essas macros com assert().

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

158
keithmo

Bem, estou surpreso que as alternativas para essa sintaxe não tenham sido mencionadas. Outro mecanismo comum (porém mais antigo) é chamar uma função que não está definida e confiar no otimizador para compilar a chamada de função se sua asserção estiver correta.

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

Embora esse mecanismo funcione (desde que as otimizações estejam ativadas), ele tem o lado negativo de não relatar um erro até você vincular, momento em que ele não consegue encontrar a definição para a função you_did_something_bad (). É por isso que os desenvolvedores de kernel começam a usar truques como as larguras de campo de bits de tamanho negativo e as matrizes de tamanho negativo (o mais recente que parou de quebrar as construções no GCC 4.4).

Em concordância com a necessidade de asserções em tempo de compilação, o GCC 4.3 introduziu o atributo de função error que permite estender este conceito antigo, mas gera um erro em tempo de compilação com uma mensagem de sua escolha - não mais mensagens de erro "matriz de tamanho negativo" enigmática!

#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)

Na verdade, a partir do Linux 3.9, agora temos uma macro chamada compiletime_assert que usa esse recurso e a maioria das macros em bug.h foram atualizadas de acordo. Ainda assim, essa macro não pode ser usada como um inicializador. No entanto, usando por expressões de instrução (outra extensão C do GCC), você pode!

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

Essa macro avaliará seu parâmetro exatamente uma vez (caso tenha efeitos colaterais) e crie um erro em tempo de compilação que diz "Eu disse a você para não me dar cinco!" se a expressão for avaliada como cinco ou não for uma constante de tempo de compilação.

Então, por que não estamos usando isso em vez de campos de bits de tamanho negativo? Infelizmente, atualmente existem muitas restrições ao uso de expressões de instrução, incluindo seu uso como inicializadores constantes (para constantes de enum, largura de campo de bits, etc.), mesmo se a expressão de instrução for completamente constante (ou seja, pode ser totalmente avaliada em tempo de compilação e de outra forma passa a __builtin_constant_p() test). Além disso, eles não podem ser usados ​​fora de um corpo de função.

Com sorte, o GCC corrigirá essas deficiências em breve e permitirá que expressões de declaração constantes sejam usadas como inicializadores constantes. O desafio aqui é a especificação da linguagem que define o que é uma expressão constante legal. C++ 11 adicionou a palavra-chave constexpr apenas para este tipo ou coisa, mas não existe nenhuma contrapartida em C11. Embora o C11 tenha obtido afirmações estáticas, o que resolverá parte desse problema, ele não resolverá todas essas deficiências. Então eu espero que o gcc possa disponibilizar uma funcionalidade constexpr como uma extensão via -std = gnuc99 & -std = gnuc11 ou algo assim e permitir seu uso em expressões de declaração et. al.

46
Daniel Santos

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

34
Matt Phillips