Вопрос: Что такое «: - !!» в коде C?


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

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


1457


источник


Ответы:


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

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


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

Ответ на keithmo здесь есть хороший ответ:

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

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


1512



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

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

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

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

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


230



Некоторые люди, похоже, смешивают эти макросы с assert(),

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


144



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

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

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


42



It's creating a size 0 bitfield if the condition is false, but a size -1 (-!!1) bitfield if the condition is true/non-zero. In the former case, there is no error and the struct is initialized with an int member. In the latter case, there is a compile error (and no such thing as a size -1 bitfield is created, of course).


30



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

0