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

1470

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

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

  • 2
    - Одинарный минус <br />! Логическое НЕ <br /> не обратное, не заданное целое число e, поэтому переменная может быть либо 0, либо 1.
  • 64
    @Lundin: git blame говорит нам, что эта конкретная форма статического утверждения была введена Яном Белихом в 8c87df4 . Очевидно, у него были веские причины для этого (см. Сообщение о коммите).
Показать ещё 11 комментариев
Теги:
linux-kernel
macros

6 ответов

1448
Лучший ответ

Это, по сути, способ проверить, может ли выражение e быть оценено как 0, а если нет, для отказа от сборки.

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

Вы должны прочитать выражение следующим образом:

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

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

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

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

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

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


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

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

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

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

  • 2
    Так как же используется этот макрос, то есть какое значение или выражение для e используется?
  • 5
    @ Weston Много разных мест. Посмотреть на себя!
Показать ещё 26 комментариев
234

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

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

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

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

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

  • 14
    sizeof(struct { int:0; }) строго соответствует?
  • 7
    Почему результат в целом будет 0 ? struct только с пустым битовым полем, правда, но я не думаю , что структура с размером 0 допускается. Например, если вы создадите массив этого типа, отдельные элементы массива все равно должны иметь разные адреса, нет?
Показать ещё 9 комментариев
155

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

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

  • 5
    И что? Какое значение имеет то, плачет ли разработчик Linux во время компиляции или плачет во время первого выполнения? Пока производственный код делает то, что должен делать.
  • 122
    @Lundin Ты шутишь? Ясно, что гораздо лучше иметь ошибку компилятора в вашем ядре , чем оставлять ее нераскрытой до тех пор, пока вы не запустите крылатую ракету, система наведения которой вызывает панику ядра.
Показать ещё 10 комментариев
40

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

#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 скоро изменит эти недостатки и позволит использовать выражения констант в качестве постоянных инициализаторов. Задача здесь - это спецификация языка, определяющая, что такое правовое постоянное выражение. С++ 11 добавил ключевое слово constexpr для этого типа или вещи, но на C11 не существует аналога. В то время как C11 действительно получил статические утверждения, которые решают часть этой проблемы, они не решат все эти недостатки. Поэтому я надеюсь, что gcc может сделать функцию constexpr доступной как расширение через -std = gnuc99 и -std = gnuc11 или некоторые из них и разрешить ее использование в выражениях выражений et. и др.

  • 6
    Все ваши решения НЕ являются альтернативами. Комментарий над макросом довольно ясен: «Вы so the expression can be used eg in a structure initializer (or where-ever else comma expressions aren't permitted). Макрос возвращает выражение типа size_t
  • 3
    @Wiz Да, я знаю об этом. Возможно, это было немного многословно, и, возможно, мне нужно еще раз посетить мою формулировку, но я хотел изучить различные механизмы статических утверждений и показать, почему мы все еще используем битовые поля отрицательного размера. Короче говоря, если мы получим механизм для выражения константного оператора, у нас будут открыты другие опции.
Показать ещё 3 комментария
30

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

  • 3
    На самом деле он возвращает size_t со значением 0, если условие истинно.
0
 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); }))

Ещё вопросы

Сообщество Overcoder
Наверх
Меню