Что ":-!!" в коде 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); }))

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

1470
задан chmurli 10 февр. '12 в 17:50
источник поделиться
6 ответов

Это, по сути, способ проверить, может ли выражение 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() - это тест времени выполнения.

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

1524
ответ дан John Feminella 10 февр. '12 в 18:04
источник поделиться

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

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

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

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

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

234
ответ дан David Heffernan 10 февр. '12 в 17:54
источник поделиться

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

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

148
ответ дан keithmo 10 февр. '12 в 18:37
источник поделиться

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

#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. и др.

42
ответ дан Daniel Santos 27 июня '13 в 11:21
источник поделиться

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

31
ответ дан Matt Phillips 10 февр. '12 в 17:54
источник поделиться
 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); }))
-1
ответ дан leesagacious 21 июня '18 в 10:18
источник поделиться

Другие вопросы по меткам