Перейти к содержанию

Утверждение (assert)

Некоторые условия в программе не должны нарушаться никогда: индекс внутри массива, указатель не нулевой, буфер не переполнен. Утверждение assert записывает такое условие прямо в код и останавливает программу в момент, когда ошибка случилась, а не через тысячу тактов, когда её последствия всплыли.

Обновлено Редакция GAW

Утверждение (assertion, assert) — проверка условия, которое при правильной работе программы всегда истинно. Если условие оказалось ложным, программа немедленно сообщает об ошибке: где, в каком файле и на какой строке. Утверждения не обрабатывают ожидаемые ошибки вроде таймаута связи — они ловят ошибки в самой программе.

assert по стандарту C

Заголовок <assert.h> определяет макрос assert(выражение). По разделу 7.2.1.1 стандарта, если выражение ложно, макрос выводит в поток ошибок текст условия, имя файла, номер строки и имя функции, а затем вызывает abort(). Если до включения заголовка определён NDEBUG, макрос превращается в ((void)0) и из кода исчезает.

assert(len <= sizeof(frame));

Проверки при компиляции

В C11 static_assert — макрос из <assert.h>, который раскрывается в ключевое слово _Static_assert (разд. 7.2); ключевым словом сам static_assert стал только в C23. Он проверяет константное выражение во время сборки и ничего не добавляет в прошивку:

#include <assert.h> /* C11: static_assert is a macro from this header */

static_assert(sizeof(struct packet) == 16, "packet layout changed");
static_assert((RX_SIZE & (RX_SIZE - 1)) == 0, "RX_SIZE must be a power of two");

Своя команда проверки есть и у компоновщика: ASSERT(выражение, "сообщение") в скрипте компоновщика останавливает сборку, если, например, для стека не осталось места.

assert в микроконтроллере

Своего потока ошибок у прошивки нет, поэтому реакцию на провал обычно переопределяют:

  1. записывают адрес или файл и строку в область ОЗУ, которую не обнуляет код запуска;
  2. выполняют точку останова — при подключённом отладчике программа остановится прямо на месте ошибки;
  3. перезапускают микроконтроллер, а после старта читают и передают сохранённую причину.

Без отладчика BKPT на Cortex-M приводит к HardFault, поэтому второй шаг выполняют только при подключённом отладчике. В модульных тестах роль утверждений играют макросы фреймворка вроде TEST_ASSERT_EQUAL_HEX16. Особенности языка C во встраиваемом коде — в статье о C для микроконтроллеров.

Частые вопросы

Что делает assert на микроконтроллере, где нет stderr?

По стандарту assert печатает сведения о провале в поток ошибок и вызывает abort. На микроконтроллере вывод идёт туда, куда перенаправлена библиотечная функция записи (UART, ITM), а abort обычно зацикливает программу. Многие проекты переопределяют обработчик провала: записывают файл и строку в неинициализируемую память, останавливаются на точке останова для отладчика и перезапускаются через сторожевой таймер.

Чем assert отличается от static_assert?

assert проверяет условие во время работы программы и стоит памяти и времени. static_assert (в C11 — макрос из <assert.h>, раскрывающийся в _Static_assert) проверяет константное выражение во время компиляции и не попадает в прошивку: например, что структура пакета занимает ровно 16 байт или размер кольцевого буфера — степень двойки.

Нужно ли отключать assert в релизной прошивке?

Стандарт даёт для этого макрос NDEBUG: если он определён до включения <assert.h>, assert превращается в ((void)0) и исчезает из кода. Но во встраиваемых системах проверки часто оставляют и в релизе, заменяя реакцию на безопасную — запись причины и перезапуск, — потому что тихое продолжение с испорченными данными опаснее.

Источники

  1. ISO/IEC 9899:201x (C11), Committee Draft N1570, April 2011 — разд. 7.2: <assert.h> определяет макросы assert и static_assert; если в точке включения определён NDEBUG, assert определяется как ((void)0); static_assert раскрывается в _Static_assert; assert должен быть макросом, а не функцией; разд. 7.2.1.1: если выражение ложно, assert выводит в стандартный поток ошибок текст аргумента, имя файла, номер строки и имя функции (__FILE__, __LINE__, __func__) и вызывает abort
  2. GNU ld 2.47 Documentation: Miscellaneous Commands (ASSERT) — ASSERT(exp, message): если выражение равно нулю, компоновщик завершается с ошибкой и выводит сообщение; пример — проверка, что для стека осталось место: ASSERT ((__stack > (_end + __stack_size)), ...)
  3. ThrowTheSwitch. Unity Test Framework v2.7.0: docs/UnityAssertionsReference.md — в модульных тестах проверки оформляют макросами TEST_ASSERT_..., например TEST_ASSERT_EQUAL_HEX16(expected, actual)

Нашли ошибку?