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:0pokude == 0; jinak1. -
-!!(e):Numericky negovat výraz z kroku 2:0pokud 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 .