Что такое ":-!!"в коде C? - PullRequest
       30

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

1594 голосов
/ 10 февраля 2012

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

Что делает :-!!?

Ответы [ 5 ]

1627 голосов
/ 10 февраля 2012

Это, по сути, способ проверить, может ли выражение 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;}: С другой стороны, если это не ноль, то это будет некоторое отрицательное число,Объявление любого битового поля с отрицательной шириной является ошибкой компиляции.

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

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

Ответ Кейтмо здесь имеет хороший ответ:

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

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

250 голосов
/ 10 февраля 2012

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

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

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

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

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

160 голосов
/ 10 февраля 2012

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

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

48 голосов
/ 27 июня 2013

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

#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 были соответственно обновлены. Тем не менее, этот макрос не может быть использован в качестве инициализатора. Однако, используя операторные выражения (другое C-расширение GCC), вы можете!

#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()). Кроме того, их нельзя использовать вне тела функции.

Надеемся, что GCC вскоре исправит эти недостатки и позволит использовать выражения константных выражений в качестве постоянных инициализаторов. Проблема здесь заключается в спецификации языка, определяющей, что является законным постоянным выражением. C ++ 11 добавил ключевое слово constexpr только для этого типа или вещи, но в C11 не существует аналога. Хотя C11 получил статические утверждения, которые решат часть этой проблемы, он не решит все эти недостатки. Поэтому я надеюсь, что gcc может сделать функциональность constexpr доступной как расширение через -std = gnuc99 & -std = gnuc11 или что-то подобное, и разрешить его использование в выражениях выражений et. и др.

34 голосов
/ 10 февраля 2012

Создает битовое поле размера 0, если условие ложно, но битовое поле размера -1 (-!!1), если условие истинно / не равно нулю. В первом случае ошибки нет, и структура инициализируется с помощью члена int. В последнем случае возникает ошибка компиляции (и, конечно, не создается такая вещь, как битовое поле размером -1).

...