Co to jest ": -!!"w kodzie C?

Natknąłem się 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 robi :-!!?

Author: mpen, 2012-02-10

6 answers

Jest to w efekcie sposób na sprawdzenie, czy wyrażenie e może być ocenione na 0, a jeśli nie, to niepowodzenie kompilacji.

Makro jest nieco błędnie nazwane; powinno być bardziej podobne do BUILD_BUG_OR_ZERO, a nie do ...ON_ZERO. (Były sporadyczne dyskusje na temat tego, czy jest to myląca nazwa.)

Powinieneś przeczytać wyrażenie w ten sposób:

sizeof(struct { int: -!!(e); }))
  1. (e): Oblicz wyrażenie e.

  2. !!(e): Logicznie neguj dwa razy: 0 if e == 0; w przeciwnym razie 1.

  3. -!!(e): numerycznie neguj wyrażenie z kroku 2: 0 jeśli było 0; w przeciwnym razie -1.

  4. struct{int: -!!(0);} --> struct{int: 0;}: jeśli jest to zero, to deklarujemy strukturę z anonimową liczbą całkowitą bitfield, która ma szerokość zero. Wszystko jest w porządku i postępujemy normalnie.

  5. struct{int: -!!(1);} --> struct{int: -1;}: z drugiej strony, Jeśli nie jest zero, to będzie to jakaś liczba ujemna. Deklarowanie dowolnego bitfielda z negative width jest błędem kompilacji.

Więc albo skończymy z bitfieldem o szerokości 0 w strukturze, co jest w porządku, albo z bitfieldem o ujemnej szerokości, co jest błędem kompilacji. Następnie bierzemy sizeof to pole, więc otrzymujemy size_t o odpowiedniej szerokości (która będzie równa zero w przypadku, gdy e jest równa zero).


Niektórzy pytali: dlaczego po prostu nie użyć assert?

Odpowiedź Keithmo tutaj ma dobra odpowiedź:

Te makra implementują test w czasie kompilacji, podczas gdy assert() jest testem w czasie wykonywania.

Dokładnie tak. Nie chcesz wykrywać problemów w jądrze podczas pracy, które mogły zostać wcześniej wykryte! To kluczowy element systemu operacyjnego. W jakim stopniu problemy można wykryć w czasie kompilacji, tym lepiej.
 1550
Author: John Feminella,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:55:00

{[0] } jest bitfield. Jeśli chodzi o !!, to jest to Podwójna negacja logiczna i tak zwraca 0 dla false lub 1 dla true. A - jest znakiem minus, czyli negacją arytmetyczną.

To tylko sztuczka, aby kompilator zwymiotował na nieprawidłowe dane wejściowe.

Rozważ BUILD_BUG_ON_ZERO. Gdy -!!(e) zostanie obliczona na wartość ujemną, spowoduje to błąd kompilacji. W przeciwnym wypadku -!!(e) oblicza się na 0, a pole bitowe o szerokości 0 ma rozmiar 0. I stąd makro ewaluuje do size_t z wartość 0.

Nazwa jest słaba moim zdaniem, ponieważ budowanie w rzeczywistości nie powiedzie się, gdy wejście jest , a nie zero.

BUILD_BUG_ON_NULL jest bardzo podobny, ale daje wskaźnik zamiast int.

 240
Author: David Heffernan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:45

Niektórzy mylą te makra z assert().

Te makra implementują test w czasie kompilacji, podczas gdy {[0] } jest testem uruchomieniowym.

 151
Author: keithmo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-08 22:22:41

Cóż, jestem dość zaskoczony, że alternatywy dla tej składni nie zostały wymienione. Innym powszechnym (ale starszym) mechanizmem jest wywołanie funkcji, która nie jest zdefiniowana i poleganie na optymalizatorze, aby skompilować wywołanie funkcji, jeśli Twoje twierdzenie jest poprawne.

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

Podczas gdy ten mechanizm działa (tak długo, jak optymalizacje są włączone), ma minusem nie zgłaszanie błędu, dopóki nie połączysz się, w tym czasie nie znajdzie definicji funkcji you_did_something_bad (). Dlatego programiści kernela zaczęli używać sztuczek takich jak pola bitowe o ujemnej wielkości i tablice o ujemnej wielkości (z których późniejsze przestały łamać Kompilacje w GCC 4.4).

W sympatii dla potrzeby twierdzeń w czasie kompilacji, GCC 4.3 wprowadził error atrybut funkcji, który pozwala rozszerzyć tę starszą koncepcję, ale generuje błąd w czasie kompilacji z wybranym przez Ciebie Komunikatem-koniec z tajemniczym błędem "negative sized array" wiadomości!

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

W rzeczywistości, od Linuksa 3.9, mamy teraz makro o nazwie compiletime_assert który wykorzystuje tę funkcję i większość makr w bug.h zostały odpowiednio zaktualizowane. Mimo to makro nie może być używane jako inicjalizacja. Jednak za pomocą wyrażenia statement (kolejny GCC C-extension), możesz!

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

To makro oceni swój parametr dokładnie raz (w przypadku, gdy ma efekty uboczne) i utworzy błąd w czasie kompilacji, który mówi " I mówiłem, żebyś nie dawał mi piątki!"jeśli wyrażenie jest obliczane na pięć lub nie jest stałą czasu kompilacji.

Dlaczego więc nie używamy tego zamiast pól bitowych o ujemnej wielkości? Niestety, obecnie istnieje wiele ograniczeń stosowania wyrażeń instrukcji, w tym ich stosowania jako stałych inicjalizatorów (dla stałych enum, szerokości pola bitowego, itd.) nawet jeśli wyrażenie instrukcji jest całkowicie stałe jego jaźń (tzn. może być w pełni obliczone w czasie kompilacji i w przeciwnym razie przechodzi __builtin_constant_p() test). Ponadto nie mogą być używane poza ciałem funkcyjnym.

Miejmy nadzieję, że GCC wkrótce poprawi te niedociągnięcia i pozwoli na używanie wyrażeń stałych jako stałych inicjatorów. Wyzwaniem jest tutaj specyfikacja języka określająca, co jest wyrażeniem stałej prawnej. W C++11 dodano słowo kluczowe constexpr tylko dla tego typu lub rzeczy, ale nie ma odpowiednika w C11. Podczas gdy C11 otrzymał statyczne twierdzenia, które rozwiążą część tego problemu, to nie rozwiąże wszystkich tych niedociągnięć. Mam więc nadzieję, że gcc może udostępnić funkcjonalność constexpr jako rozszerzenie poprzez -std = gnuc99 & - std=gnuc11 lub coś takiego i zezwolić na jej użycie na wyrażeniach instrukcji et. al.

 42
Author: Daniel Santos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-17 22:08:28

Tworzy rozmiar 0 bitfield jeśli warunek jest false, ale Rozmiar -1 (-!!1) bitfield, jeśli warunek jest true / non-zero. W pierwszym przypadku, nie ma błędu i struktura jest inicjalizowana przez element int. W tym drugim przypadku występuje błąd kompilacji(i oczywiście nie tworzy się czegoś takiego jak rozmiar -1 bitfield).

 31
Author: Matt Phillips,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-10 14:56:35
 Linux Kernel :   

/* 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); }))
 -4
Author: leesagacious,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-24 11:07:40