Что такое ":-!!- в коде Си?

я наткнулся на этот странный макрокод в / usr / include / linux / ядро.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); }))

Что значит :-!! сделать?

6 ответов


это, по сути, способ проверить, может ли выражение e быть оценено как 0, а если нет, то сбой сборки.

макрос несколько неправильно назван; это должно быть что-то вроде BUILD_BUG_OR_ZERO, а не ...ON_ZERO. (Были случайные дискуссии о том, является ли это название.)

вы должны прочитать выражение так:

sizeof(struct { int: -!!(e); }))
  1. (e): Вычислить выражение e.

  2. !!(e): логически отрицать дважды:0 если e == 0, иначе 1.

  3. -!!(e): численно отрицать выражение из шага 2:0 если бы это было 0, иначе -1.

  4. struct{int: -!!(0);} --> struct{int: 0;}: если он равен нулю, то мы объявляем структуру с анонимным целочисленным битовым полем с нулевой шириной. Все в порядке, и мы переходим в нормальный.

  5. struct{int: -!!(1);} --> struct{int: -1;}: С другой стороны, если это не ноль, то это будет какое-то отрицательное число. Объявление любого bitfield с отрицательный width является ошибкой компиляции.

таким образом, мы либо получим битовое поле с шириной 0 в структуре, что нормально, либо битовое поле с отрицательной шириной, что является ошибкой компиляции. Тогда берем sizeof это поле, поэтому мы получаем size_t с соответствующим ширина (которая будет равна нулю в случае, когда e равна нулю).


некоторые спрашивали:почему бы просто не использовать assert?

keithmo это здесь есть хороший ответ:

эти макросы реализуют тест времени компиляции, в то время как assert() является тестом времени выполнения.

совершенно верно. Вы не хотите обнаруживать проблемы в своем ядро во время выполнения это могло быть поймали раньше! Это важная часть операционной системы. В какой степени проблемы могут быть обнаружены во время компиляции, тем лучше.


на : является bitfield. Что касается !!, что составляет логические двойное отрицание и так возвращает 0 для false или 1 для true. И - - знак минус, то есть арифметическое отрицание.

это всего лишь трюк, чтобы заставить компилятор блевать на недопустимых входах.

считают BUILD_BUG_ON_ZERO. Когда -!!(e) вычисляет отрицательное значение, которое вызывает ошибку компиляции. В противном случае -!!(e) вычисляет значение 0, а битовое поле ширины 0 имеет размер 0. И, следовательно, макрос оценивается как size_t со значением 0.

имя слабое на мой взгляд, потому что сборка на самом деле терпит неудачу, когда вход не ноль.

BUILD_BUG_ON_NULL очень похоже, но дает указатель, а не int.


некоторые люди, похоже, путают эти макросы с assert().

эти макросы реализуют тест времени компиляции, в то время как assert() тест время выполнения.


Ну, я очень удивлен, что альтернативы этому синтаксису не были упомянуты. Другой распространенный (но более старый) механизм-вызвать функцию, которая не определена, и полагаться на оптимизатор для компиляции вызова функции, если ваше утверждение верно.

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

пока этот механизм работает (пока включены оптимизации), он имеет обратную сторону-не сообщать об ошибке, пока вы не свяжетесь, и в это время он не сможет найти определение для функции you_did_something_bad(). Вот почему разработчики ядра начинают использовать такие трюки, как ширина битового поля отрицательного размера и массивы отрицательного размера (более поздние из которых перестали ломать сборки в GCC 4.4).

в сочувствии к необходимости утверждения времени компиляции, GCC 4.3 представил error свойства функция это позволяет вам расширить эту более старую концепцию, но генерировать ошибку времени компиляции с сообщением по вашему выбору - нет более загадочного " отрицательного размер массива" сообщения об ошибках!

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

на самом деле, начиная с Linux 3.9, у нас теперь есть макрос под названием compiletime_assert, который использует эту функцию, и большинство макросов в bug.h были обновлены соответствующим образом. Тем не менее, этот макрос нельзя использовать в качестве инициализатора. Однако, используя заявление выражений (другой GCC c-расширение), вы можете!

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

этот макрос будет оценить свой параметр ровно один раз (в случае он имеет побочные эффекты) и создает ошибку времени компиляции, которая говорит: "Я говорил вам не давать мне пять!"если выражение оценивается в пять или не является константой времени компиляции.

так почему мы не используем это вместо битовых полей отрицательного размера? Увы, в настоящее время существует множество ограничений использования выражений операторов, включая их использование в качестве постоянных инициализаторов (для констант перечисления, ширины битового поля и т. д.) даже если выражение утверждения полностью постоянно, его самость (т. е. может быть полностью оцененным во время компиляции и в противном случае передает __builtin_constant_p()


он создает размер 0 bitfield, если условие false, но размер -1 (-!!1) bitfield, если условие true/ненулевое. В первом случае ошибки нет, и структура инициализируется членом int. В последнем случае возникает ошибка компиляции (и нет такой вещи, как size -1 bitfield создается, конечно).


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