Ответ 1
Простое объяснение состоит в том, что для стандартного требования assert
должен быть макрос, если мы посмотрим на черновик стандарта C99 (as насколько я могу сказать, что разделы одинаковы в черновик стандарта C11) раздел 7.2
Диагностика в параграфе 2 гласит:
Макрос утверждения должен быть реализован как макрос, а не как фактический функция. Если определение макроса подавляется, чтобы получить доступ к фактическая функция, поведение undefined.
Зачем это требуется, обоснование, приведенное в Обоснование для языков международного стандартного программирования-C:
Это может быть сложно или невозможно сделать утвердительной истинной функцией, поэтому она ограничена макросом форма.
который не очень информативен, но мы можем видеть из других требований, почему. Возвращаясь к разделу 7.2
, параграф 1 говорит:
[...] Если NDEBUG определяется как имя макроса в точке исходного файла где включено, макрос assert определяется просто как
#define assert(ignore) ((void)0)
Макрос утверждения переопределяется в соответствии с текущим состоянием NDEBUG каждый раз, когда это включено.
Это важно, так как это позволяет нам легко отключить утверждения в режиме выпуска, где вам может потребоваться стоимость потенциально дорогостоящих проверок.
и второе важное требование состоит в том, что требуется использовать макросы __FILE__
, __LINE__
и __func__
, которые описаны в разделе 7.2.1.1
Макрос утверждения, который гласит:
[...] макрос подтверждения записывает информацию о конкретном вызове которые потерпели неудачу [...], последние являются соответственно значениями препроцессорные макросы __FILE_ _ и __LINE_ _ и идентификатора __func_ _) в стандартном потоке ошибок в определенном для реализации формате. 165) Затем он вызывает функцию прерывания.
где примечание 165
гласит:
Записанное сообщение может иметь вид:
Assertion failed: expression, function abc, file xyz, line nnn.
Наличие макроса позволяет макросам __FILE__
и т.д. оцениваться в нужном месте, а Joachim указывает, что макрос позволяет ему вставлять исходное выражение в сообщение, которое оно генерирует.
В проекте стандарта С++ требуется, чтобы содержимое заголовка cassert
совпало с заголовком assert.h
из библиотеки Standrd C:
Содержимое совпадает с заголовком библиотеки Standard C.
См. также: ISO C 7.2.
Почему (void) 0?
Зачем использовать (void)0
в отличие от другого выражения, которое ничего не делает? Мы можем придумать несколько причин: во-первых, как выглядит синопсис утверждения в разделе 7.2.1.1
:
void assert(scalar expression);
и он говорит (внимание мое):
Макрос утверждений ставит диагностические тесты в программы; он расширяется до выражения void.
выражение (void)0
согласуется с необходимостью получить выражение void.
Предполагая, что у нас не было этого требования, другие возможные выражения могут иметь нежелательные эффекты, такие как разрешение использования assert
в режиме деблокирования, которое не было бы разрешено в режиме отладки, например, используя plain 0
позволил бы нам использовать assert
в задании и при правильном использовании скорее всего создаст предупреждение expression result unused
. Что касается использования составного оператора в качестве комментария, мы можем видеть из многострочного макроса C: do/while (0) vs scope block, что они имеют нежелательные эффекты в некоторых случаях.