Zdá se, že někteří lidé si tato makra pletou s assert()
.
Tato makra implementují test v době kompilace, zatímco assert()
je běhový test.
Toto je ve skutečnosti způsob, jak zkontrolovat, zda lze výraz e vyhodnotit jako 0, a pokud ne, sestavení se nezdaří .
Makro je poněkud špatně pojmenováno; mělo by to být něco jako BUILD_BUG_OR_ZERO
, spíše než ...ON_ZERO
. (Občas se vedly diskuse o tom, zda se nejedná o matoucí název .)
Měli byste číst výraz takto:
sizeof(struct { int: -!!(e); }))
-
(e)
:Vypočítejte výraze
. -
!!(e)
:Logicky dvakrát negovat:0
pokude == 0
; jinak1
. -
-!!(e)
:Numericky negovat výraz z kroku 2:0
pokud to bylo0
; jinak-1
. -
struct{int: -!!(0);} --> struct{int: 0;}
:Pokud by byla nula, pak deklarujeme strukturu s anonymním celočíselným bitovým polem, které má šířku nula. Vše je v pořádku a pokračujeme jako obvykle. -
struct{int: -!!(1);} --> struct{int: -1;}
:Na druhou stranu, pokud není nula, pak to bude nějaké záporné číslo. Deklarování jakéhokoli bitového pole pomocí záporné šířka je chyba kompilace.
Buď tedy skončíme s bitovým polem, které má ve struktuře šířku 0, což je v pořádku, nebo bitovým polem se zápornou šířkou, což je chyba kompilace. Pak vezmeme sizeof
toto pole, takže dostaneme size_t
s příslušnou šířkou (která bude nula v případě e
je nula).
Někteří lidé se ptali:Proč prostě nepoužít assert
?
Keithmova odpověď zde má dobrou odezvu:
Tato makra implementují test v době kompilace, zatímco sustain() je test za běhu.
Naprosto správně. Nechcete zjišťovat problémy ve vašem jádru za běhu, který mohl být zachycen dříve! Je to kritická součást operačního systému. V jakékoli míře lze problémy detekovat v době kompilace, tím lépe.
No, docela mě překvapuje, že nebyly zmíněny alternativy k této syntaxi. Dalším běžným (ale starším) mechanismem je volání funkce, která není definována, a spoléhání se na optimalizátor, že volání funkce zkompiluje, pokud je vaše tvrzení správné.
#define MY_COMPILETIME_ASSERT(test) \
do { \
extern void you_did_something_bad(void); \
if (!(test)) \
you_did_something_bad(void); \
} while (0)
I když tento mechanismus funguje (pokud jsou povoleny optimalizace), má nevýhodu v tom, že nehlásí chybu, dokud nepropojíte odkaz, v tom okamžiku se mu nepodaří najít definici funkce you_did_something_bad(). To je důvod, proč vývojáři jádra začínají používat triky, jako jsou záporné šířky bitového pole a pole záporné velikosti (pozdější z nich přestalo narušovat sestavení v GCC 4.4).
V pochopení pro potřebu asercí v době kompilace zavedl GCC 4.3 error
atribut function, který vám umožní rozšířit tento starší koncept, ale vygenerovat chybu při kompilaci se zprávou dle vašeho výběru – žádné další záhadné chybové zprávy „negative size array“!
#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)
Ve skutečnosti od Linuxu 3.9 nyní máme makro nazvané compiletime_assert
který používá tuto funkci a většinu maker v bug.h
byly odpovídajícím způsobem aktualizovány. Přesto toto makro nelze použít jako inicializátor. Nicméně pomocí výrazů příkazů (další GCC C-extension), můžete!
#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; \
})
Toto makro vyhodnotí svůj parametr přesně jednou (v případě, že má vedlejší účinky) a vytvoří chybu při kompilaci, která říká "Říkal jsem ti, abys mi nedával pět!" pokud je výraz vyhodnocen jako pět nebo není konstantou v době kompilace.
Proč tedy nepoužíváme toto místo bitových polí záporné velikosti? Bohužel v současné době existuje mnoho omezení použití výrazů příkazu, včetně jejich použití jako konstantních inicializátorů (pro výčtové konstanty, šířku bitového pole atd.), i když je výraz příkazu zcela konstantní sám o sobě (tj. může být plně vyhodnocen v době kompilace a jinak předá __builtin_constant_p()
test). Dále je nelze použít mimo tělo funkce.
Doufejme, že GCC tyto nedostatky brzy opraví a umožní použití konstantních výrazů příkazů jako konstantních inicializátorů. Problémem je zde jazyková specifikace definující, co je právní konstantní výraz. C++11 přidal klíčové slovo constexpr právě pro tento typ nebo věc, ale žádný protějšek v C11 neexistuje. I když C11 získal statická tvrzení, která vyřeší část tohoto problému, nevyřeší všechny tyto nedostatky. Takže doufám, že gcc dokáže zpřístupnit funkci constexpr jako rozšíření přes -std=gnuc99 &-std=gnuc11 nebo něco podobného a umožní její použití na výrazech příkazů et. al.
:
je bitfield. Pokud jde o !!
, což je logická dvojitá negace, a tak vrací 0
pro false nebo 1
za pravdu. A -
je znaménko mínus, tj. aritmetická negace.
Všechno je to jen trik, jak přimět kompilátor k barfování o neplatných vstupech.
Zvažte BUILD_BUG_ON_ZERO
. Když -!!(e)
vyhodnotí se na zápornou hodnotu, což způsobí chybu kompilace. Jinak -!!(e)
se vyhodnotí na 0 a bitové pole šířky 0 má velikost 0. Makro se tedy vyhodnotí jako size_t
s hodnotou 0.
Název je podle mého názoru slabý, protože sestavení ve skutečnosti selže, když vstup není nula.
BUILD_BUG_ON_NULL
je velmi podobný, ale dává spíše ukazatel než int
.