Мой преподаватель спросил меня, что в классе, и мне было интересно, почему это макрос вместо функции?
Почему утверждается макрос, а не функция?
Ответ 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, что они имеют нежелательные эффекты в некоторых случаях.
Ответ 2
- Он позволяет захватывать файл (через
__FILE__
) и номер строки (через__LINE__
) - Он позволяет заменить
assert
на допустимое выражение, которое ничего не делает (т.е.((void)0)
) при создании в режиме выпуска
Ответ 3
Этот макрос отключен, если в момент включения макрос с именем NDEBUG уже определен. Это позволяет кодеру включать в себя столько вызовов assert, сколько необходимо в исходном коде при отладке программы, а затем отключить их все для производственной версии, просто включив такую строку, как:
#define NDEBUG
в начале своего кода, перед включением <assert.h>
.
Поэтому этот макрос предназначен для захвата ошибок программирования, а не ошибок пользователя или времени выполнения, поскольку он обычно отключается после того, как программа выходит из фазы отладки.
Выполнение этой функции увеличивает некоторые вызовы функций, и вы не можете управлять всеми такими утверждениями в режиме деблокирования.
Если вы используете функцию, то _FILE__
, __LINE__
и __func__
выдаст значение этого кода функции assert. Не эта линия вызова или вызывающая функциональная строка.
Ответ 4
Некоторые утверждения могут быть дорогими для вызова. Вы только что написали процедуру инверсии матрицы с высокой производительностью, и вы добавили проверку работоспособности
assert(is_identity(matrix * inverse))
до конца. Ну, ваши матрицы довольно большие, и если assert
- это функция, для выполнения вычисления потребуется много времени, прежде чем передать его в assert. Время, которое вы действительно не хотите тратить, если вы не выполняете отладки.
Или, может быть, утверждение относительно дешево, но оно содержится в очень короткой функции, которая будет вызвана во внутреннем цикле. Или другие подобные обстоятельства.
Сделав вместо этого assert
макрос, вы можете полностью исключить вычисление, когда утверждения отключены.
Ответ 5
Почему утверждается макрос, а не функция?
Поскольку он должен быть скомпилирован в режиме DEBUG и не должен компилироваться в режиме RELEASE.