Wpadłem na ten dziwny kod makra w /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); }))
Co ma :-!!
zrobić?
c
linux
macros
linux-kernel
chmurli
źródło
źródło
sizeof
„ocenia” typ, ale nie wartość. Jest to typ nieprawidłowy w tym przypadku.Odpowiedzi:
Jest to w efekcie sposób sprawdzenia, czy wyrażenie e może być ocenione na 0, a jeśli nie, niepowodzenie kompilacji .
Makro jest nieco źle nazwane; powinno to być coś bardziej podobnego
BUILD_BUG_OR_ZERO
niż...ON_ZERO
. (Od czasu do czasu dyskutowano o tym, czy to mylące imię ).Powinieneś przeczytać takie wyrażenie:
(e)
: Oblicz wyrażeniee
.!!(e)
: Logicznie zaneguj dwukrotnie:0
ife == 0
; inaczej1
.-!!(e)
: Zaneguj liczbowo wyrażenie z kroku 2:0
jeśli było0
; inaczej-1
.struct{int: -!!(0);} --> struct{int: 0;}
: Jeśli było zero, deklarujemy strukturę z anonimowym całkowitym polem bitowym o szerokości zero. Wszystko jest w porządku i postępujemy jak zwykle.struct{int: -!!(1);} --> struct{int: -1;}
: Z drugiej strony, jeśli nie jest to zero, to będzie to pewna liczba ujemna. Zadeklarowanie dowolnego pola bitowego o ujemnej szerokości jest błędem kompilacji.Więc albo skończymy z bitem o szerokości 0 w strukturze, co jest w porządku, albo z bitem o ujemnej szerokości, co jest błędem kompilacji. Następnie bierzemy
sizeof
to pole, więc otrzymujemysize_t
odpowiednią szerokość (która będzie wynosić zero w przypadku, gdye
wynosi zero).Niektóre osoby pytają: dlaczego po prostu nie użyć
assert
?Odpowiedź keithmo tutaj ma dobrą odpowiedź:
Dokładnie tak. Nie chcesz wykrywać problemów w jądrze w czasie wykonywania, które mogły zostać wcześniej wykryte ! To kluczowy element systemu operacyjnego. W jakimkolwiek stopniu problemy można wykryć w czasie kompilacji, tym lepiej.
źródło
static_assert
do podobnych celów.!
,<
,>
,<=
,>=
,==
,!=
,&&
,||
) zawsze dają wartość 0 lub 1. Inne wyrażenia może dać wyniki, które mogą być stosowane jako warunkach, ale jedynie zero lub niezerowe; na przykład,isdigit(c)
gdziec
jest cyfrą, może dać dowolną niezerową wartość (która jest następnie traktowana jako prawdziwa w warunku).To
:
jest pole bitowe. Jeśli chodzi o!!
, to jest logiczne podwójna negacja i tak wraca0
do fałszywych lub1
za prawdziwe. A-
to znak minus, tzn. Negacja arytmetyczna.To tylko sztuczka, aby kompilator przestał działać przy nieprawidłowych danych wejściowych.
Zastanów się
BUILD_BUG_ON_ZERO
. Gdy zostanie-!!(e)
oszacowana na wartość ujemną, spowoduje to błąd kompilacji. W przeciwnym razie-!!(e)
wartość jest równa 0, a pole bitowe o szerokości 0 ma rozmiar 0. A zatem makrosize_t
ma wartość 0.Moim zdaniem nazwa jest słaba, ponieważ kompilacja faktycznie kończy się niepowodzeniem, gdy dane wejściowe nie są równe zero.
BUILD_BUG_ON_NULL
jest bardzo podobny, ale daje wskaźnik zamiast anint
.źródło
sizeof(struct { int:0; })
ściśle zgodny?0
? Astruct
z pustym polem bitowym, to prawda, ale nie sądzę, że struktura o rozmiarze 0 jest dozwolona. Np. Jeśli utworzyłbyś tablicę tego typu, poszczególne elementy tablicy wciąż muszą mieć różne adresy, nie?0
szerokości, ale nie, jeśli w strukturze nie ma żadnego innego nazwanego elementu.(C99, 6.7.2.1p2) "If the struct-declaration-list contains no named members, the behavior is undefined."
Na przykładsizeof (struct {int a:1; int:0;})
jest ściśle zgodny, alesizeof(struct { int:0; })
nie jest (zachowanie nieokreślone).Wydaje się, że niektórzy mylą te makra
assert()
.Te makra implementują test czasu kompilacji, podczas gdy
assert()
jest to test czasu wykonywania.źródło
Cóż, jestem dość zaskoczony, że nie wspomniano o alternatywach dla tej składni. Innym powszechnym (ale starszym) mechanizmem jest wywoływanie funkcji, która nie została zdefiniowana i poleganie na optymalizatorze w celu skompilowania wywołania funkcji, jeśli twoje twierdzenie jest poprawne.
Chociaż ten mechanizm działa (o ile włączone są optymalizacje), jego wadą jest nie zgłaszanie błędu do momentu połączenia, w którym to czasie nie można znaleźć definicji funkcji you_did_something_bad (). Dlatego programiści jądra zaczynają używać sztuczek, takich jak szerokości pola bitowego o ujemnej wielkości i tablice o rozmiarze ujemnym (późniejsze przestały łamać kompilacje w GCC 4.4).
W odpowiedzi na potrzebę asercji w czasie kompilacji, GCC 4.3 wprowadził
error
atrybut funkcji, który pozwala rozwinąć tę starszą koncepcję, ale generuje błąd czasu kompilacji z wybranym przez Ciebie komunikatem - nigdy więcej tajemniczej „ujemnej tablicy” „komunikaty o błędach!W rzeczywistości, począwszy od Linuksa 3.9, mamy teraz wywoływane makro,
compiletime_assert
które korzysta z tej funkcji, a większość makrbug.h
została odpowiednio zaktualizowana. Tego makra nie można jednak używać jako inicjalizatora. Jednak używając wyrażeń instrukcji (inne rozszerzenie C GCC), możesz!To makro dokona oceny swojego parametru dokładnie raz (w przypadku, gdy ma skutki uboczne) i utworzy błąd kompilacji, który mówi: „Mówiłem, żebyś nie dawał mi pięciu!” jeśli wyrażenie ma wartość pięć lub nie jest stałą czasu kompilacji.
Dlaczego więc nie używamy tego zamiast ujemnych pól bitowych? Niestety, istnieje obecnie wiele ograniczeń użycia wyrażeń instrukcji, w tym ich użycia jako stałych inicjatorów (dla stałych wyliczeniowych, szerokości pola bitowego itp.), Nawet jeśli wyrażenie instrukcji jest całkowicie stałe, to znaczy, że można je w pełni ocenić w czasie kompilacji i w inny sposób przechodzi
__builtin_constant_p()
test). Ponadto nie można ich używać poza ciałem funkcji.Mamy nadzieję, że GCC niedługo poprawi te niedociągnięcia i zezwoli na stosowanie stałych wyrażeń instrukcji jako stałych inicjatorów. Wyzwaniem jest tutaj specyfikacja języka określająca, co jest stałym wyrażeniem prawnym. C ++ 11 dodał słowo kluczowe constexpr tylko dla tego typu lub rzeczy, ale w C11 nie istnieje odpowiednik. Podczas gdy C11 otrzymał statyczne twierdzenia, które rozwiążą część tego problemu, nie rozwiąże wszystkich tych niedociągnięć. Mam więc nadzieję, że gcc może udostępnić funkcjonalność constexpr jako rozszerzenie poprzez -std = gnuc99 i -std = gnuc11 lub inne, i umożliwić jej użycie w wyrażeniach instrukcji et. glin.
źródło
so the expression can be used e.g. in a structure initializer (or where-ever else comma expressions aren't permitted).
„ Makro zwraca wyrażenie typusize_t
error: bit-field ‘<anonymous>’ width not an integer constant
Pozwala to tylko na stałe. Więc jaki jest pożytek?Tworzy pole
0
bitowe wielkości, jeśli warunek jest fałszywy, ale pole bitowe wielkości-1
(-!!1
), jeśli warunek jest prawdziwy / niezerowy. W pierwszym przypadku nie ma błędu, a struktura jest inicjowana za pomocą elementu int. W tym drugim przypadku występuje błąd kompilacji (i-1
oczywiście nie powstaje coś takiego jak pole bitowe wielkości ).źródło
size_t
wartość 0, jeśli warunek jest spełniony.