Eu encontrei esse código de macro estranho 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?
c
linux
macros
linux-kernel
chmurli
fonte
fonte
sizeof
"avalia" o tipo, mas não o valor. É o tipo que é inválido neste caso.Respostas:
Esta é, com efeito, 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 errada; deveria ser algo mais parecido
BUILD_BUG_OR_ZERO
, ao invés de...ON_ZERO
. (Houve discussões ocasionais sobre se esse é um nome confuso .)Você deve ler a expressão assim:
(e)
: Computar expressãoe
.!!(e)
: Negar logicamente duas vezes:0
see == 0
; caso contrário1
.-!!(e)
: Negue numericamente a expressão da etapa 2:0
se foi0
; caso contrário-1
.struct{int: -!!(0);} --> struct{int: 0;}
: Se fosse zero, declaramos uma estrutura com um campo de bits inteiro anônimo que possui largura zero. Está tudo bem e prosseguimos normalmente.struct{int: -!!(1);} --> struct{int: -1;}
: Por outro lado, se não for zero, será um número negativo. Declarar qualquer campo de bits com largura negativa é um erro de compilação.Portanto, terminaremos com um campo de bits que possui largura 0 em uma estrutura, o que é bom ou um campo de bits com largura negativa, que é um erro de compilação. Em seguida, pegamos
sizeof
esse campo e obtemos umsize_t
com a largura apropriada (que será zero no caso em quee
é zero).Algumas pessoas perguntaram: Por que não usar apenas um
assert
?A resposta de keithmo aqui tem uma boa resposta:
Exatamente certo. Você não deseja detectar problemas no seu kernel em tempo de execução que poderiam ter sido detectados anteriormente! É uma parte crítica do sistema operacional. Em qualquer extensão, os problemas podem ser detectados em tempo de compilação, tanto melhor.
fonte
static_assert
para fins relacionados.!
,<
,>
,<=
,>=
,==
,!=
,&&
,||
) sempre produzem 0 ou 1. Outras expressões podem proporcionar resultados que podem ser utilizados como um condições, mas são meramente zero ou diferente de zero; por exemplo,isdigit(c)
ondec
é um dígito, pode gerar qualquer valor diferente de zero (que é tratado como verdadeiro em uma condição).O
:
é um campo de bits. Quanto a!!
, isso é dupla negação lógica e, portanto, retorna0
para falso ou1
verdadeiro. E o-
é um sinal de menos, ou seja, negação aritmética.É apenas um truque para fazer com que o compilador vomite 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, será-!!(e)
avaliado como 0, e um campo de bits com largura 0 terá tamanho 0 e, portanto, a macro será avaliada como asize_t
com valor 0.O nome é fraco, na minha opinião, porque a construção falha de fato quando a entrada não é zero.
BUILD_BUG_ON_NULL
é muito semelhante, mas gera um ponteiro em vez de umint
.fonte
sizeof(struct { int:0; })
estritamente em conformidade?0
? Astruct
com apenas um campo de bits vazio, é verdade, mas não acho que a estrutura com tamanho 0 seja permitida. Por exemplo, se você criar uma matriz desse tipo, os elementos individuais da matriz ainda deverão ter endereços diferentes, não?0
largura de largura, mas não se não houver outro membro nomeado na estrutura.(C99, 6.7.2.1p2) "If the struct-declaration-list contains no named members, the behavior is undefined."
Por exemplo,sizeof (struct {int a:1; int:0;})
é estritamente conforme, massizeof(struct { int:0; })
não é (comportamento indefinido).Algumas pessoas parecem estar confundindo essas macros
assert()
.Essas macros implementam um teste em tempo de compilação, enquanto
assert()
é um teste em tempo de execução.fonte
Bem, estou surpreso que as alternativas a essa sintaxe não tenham sido mencionadas. Outro mecanismo comum (mas mais antigo) é chamar uma função que não está definida e confiar no otimizador para compilar a chamada de função se sua afirmação estiver correta.
Embora esse mecanismo funcione (desde que as otimizações estejam ativadas), ele tem a desvantagem de não relatar um erro até você vincular, quando não consegue encontrar a definição para a função you_did_something_bad (). É por isso que os desenvolvedores do kernel começam a usar truques, como as larguras de campo de bits de tamanho negativo e as matrizes de tamanho negativo (o último dos quais parou de quebrar as compilações no GCC 4.4).
Simpatizando com a necessidade de asserções em tempo de compilação, o GCC 4.3 introduziu o
error
atributo function que permite estender esse conceito mais antigo, mas gera um erro em tempo de compilação com uma mensagem de sua escolha - não mais uma matriz de tamanho negativo "enigmática" " mensagens de erro!De fato, a partir do Linux 3.9, agora temos uma macro chamada
compiletime_assert
que usa esse recurso e a maioria das macrosbug.h
foram atualizadas de acordo. Ainda assim, essa macro não pode ser usada como inicializador. No entanto, usando expressões de declaração (outra extensão C do GCC), você pode!Essa macro avaliará seu parâmetro exatamente uma vez (caso tenha efeitos colaterais) e criará um erro em tempo de compilação que diz "Eu disse para você não me dar cinco!" se a expressão for avaliada como cinco ou não for uma constante em 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 o uso como inicializadores constantes (para constantes enum, largura de campo de bits etc.), mesmo que a expressão de instrução seja completamente constante, ela própria (ou seja, pode ser totalmente avaliada em tempo de compilação e passa no
__builtin_constant_p()
teste). Além disso, eles não podem ser usados fora de um corpo funcional.Felizmente, o GCC alterará essas deficiências em breve e permitirá que expressões constantes de instruções sejam usadas como inicializadores constantes. O desafio aqui é a especificação da linguagem que define o que é uma expressão constante legal. O C ++ 11 adicionou a palavra-chave constexpr apenas para esse tipo ou coisa, mas nenhuma contrapartida existe no 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, espero que o gcc possa disponibilizar uma funcionalidade constexpr como uma extensão via -std = gnuc99 & -std = gnuc11 ou algo parecido e permitir seu uso nas expressões de declaração et. al.
fonte
so the expression can be used e.g. in a structure initializer (or where-ever else comma expressions aren't permitted).
" A macro retorna uma expressão do tiposize_t
error: bit-field ‘<anonymous>’ width not an integer constant
Está permitindo apenas constantes. Então, qual é a utilidade?Ele está criando um campo de
0
bits de tamanho se a condição for falsa, mas um campo de bit 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-1
, naturalmente , não existe um campo de bits de tamanho ).fonte
size_t
com valor 0, caso a condição seja verdadeira.