Assert: что это такое? Печать
Добавил(а) microsin   

В некоторых программах присутствуют вызовы assert (или ASSERT). В этой статье собрана информация из разных источников, поясняющая смысл и принцип использования этой операции на языке C/C++.

"Assert" в переводе с английского означает "утверждать". В контексте программирования на C/C++ вызов assert означает, что какое-то условие в этом месте обязательно верно, и если это не так, то дальнейшее выполнение программы невозможно. Таким образом, assert прервет нормальную работу программы (иногда с выводом сообщения, которое указано в дополнительном параметре аргумента assert), если условие, которое проверяет assert, равно false. Обычно эта техника используется только для отладки (код assert компилируется только для конфигурации Debug проекта), чтобы отследить возможные проблемные места в программе.

Прототип функции assert() часто выглядит так (обычно это макрос):

void assert (int expression);

Если аргумент expression этого макроса вычисляется как 0 (т. е. становится false), то нормальный поток вычислений останавливается. Если это среда выполнения PC, то при этом в стандартный поток ошибок (stderr) будет выведено сообщение об ошибке, и будет вызвана функция abort, завершающая выполнение программы.

Специфика содержания сообщения зависит от конкретной реализации библиотеки кода реального времени выполнения (runtime library), но обычно подразумевается, что оно должно включать в себя как минимум выражение, которое ошибочно, имя модуля исходного кода, где произошла ошибка, и номер строки, где эта ошибка произошла. Простейшая реализация библиотеки (например, во встраиваемых приложениях) может вообще не генерировать сообщение об ошибке. Пример обычного формата сообщения об ошибке:

Assertion failed: выражение, имя_файла, номер_строки_файла

Функционал макроса становится доступен в момент подключения заголовочного файла assert.h. Если в момент подключения заголовка определено имя NDEBUG, то весь код операторов assert из программы выбрасывается. Это позволяет программисту на этапе отладки вставлять в код множество избыточных проверок, и впоследствии запретить их все, когда выпускается рабочий релиз программы. Это делается простым добавлением в код следующего определения (непосредственно перед подключением заголовочного файла assert.h):

#define NDEBUG

Таким образом, этот макрос разработан для захвата ошибок программирования, не ошибок пользователя или ошибок реального времени выполнения, поскольку весь этот механизм полностью запрещается после выхода программы из фазы отладки.

Пример использования assert:

#include < stdio.h >      /* определение printf */
#include < assert.h >     /* определение assert */
void print_number(int* myInt)
{
   assert (myInt!=NULL);
   printf ("%d\n",*myInt);
}
 
int main ()
{
   int a=10;
   int *b = NULL;
   int *c = NULL;
 
   b=&a;
 
   // Этот вызов функции отработает нормально:
   print_number (b);
   // Здесь произойдет срабатывание проверки assert:
   print_number (c);
 
   return 0;
}

Когда параметр assert становится false (в данном примере происходит, если передаваемый в функцию print_number указатель равен NULL), программа останавливается. Будет выведено сообщение об ошибке, и работа программы прервется.

Во встраиваемых приложениях остановка программы по ошибке в assert обычно заключается в бесконечном зацикливании в том месте кода, где проверка assert показала ошибку. Для диагностики ошибки можно остановить выполнение в отладчике, и посмотреть, в каком месте программы произошло зацикливание. Реже реализация assert предусматривает дополнительный параметр в виде текстового сообщения, которое поясняет причину возникновения ошибки. Это сообщение обычно выводится в отладочную консоль (на экран или в последовательный порт UART). Пример:

assert("Длина не может быть отрицательна!", length >= 0);

Когда выпускается релиз проекта (конфигурация Release), то все вычисления, связанные с проверками assert, из программы удаляются. Это делается с помощью операторов условной компиляции путем проверки наличия макроопределения наподобие NDEBUG, NODEBUG, DEBUG и т. п. Будьте внимательны: программа никогда не должна полагаться на запуск кода в проверках assert, иначе релиз нормально работать не будет.

Для чего нужен assert? Код, который вставляется вызовами assert(), предназначен для вылавливания багов в программе во время выполнения кода в реальном времени (runtime). Т. е. могут отслеживаться ошибки, являющиеся результатом вычислений в программе (что отличается от ошибок времени компиляции). В конфигурации релиза весь код, который реализует assert, из программы выбрасывается.

Как использовать assert? Достаточно следовать нескольким простым правилам:

1. Старайтесь вставлять assert() перед телом цикла, иначе assert может значительно снизить скорость выполнения программы.

2. Не пишите программу так, что её выполнение зависит от кода assert(). Например, следующий вызов assert будет ошибочным:

assert(++length >= 0);

Вместо этого следует писать так:

++length;
assert(length >= 0);

Причина понятна - в релизе код assert будет выброшен, так что length не получит инкремента. В результате программа будет работать по-другому, если будет работать вообще.

3. Всегда проверяйте, что для релиза код assert() выбрасывается из исполняемого кода.

[Что внутри assert?]

В зависимости от среды разработки, языка, подключенных библиотек и определений тело assert может работать по-разному. Например, в приложении PC на языке C++ может вызываться исключение (throw/raise excepton), на языке C может происходить останов выполнения программы с выходом (abort, exit). Во встраиваемых приложениях (микроконтроллеры) обычной практикой реализации assert может быть простое бесконечное зацикливание, иногда это реализуется с выводом подходящего сообщения в последовательный порт (отладочную консоль, UART).

Множество компиляторов и поставляемых вместе с ними библиотек предоставляют assert в виде макроса. Макрос assert() (или ASSERT()) вернет true, если его проверяемый параметр true, и будет выполнять некие (аварийные) действия, если проверяемый параметр оказался вдруг false.

Большинство реализаций макроса assert предусматривает проверку наличия макроопределения NDEBUG. Если, к примеру, это макроопределение не задано (в конфигурации проекта или общем заголовочном файле), то код макроса assert() будет добавлен в программу. Если же макроопределение NDEBUG присутствует (когда выпускается релиз проекта), то препроцессор компилятора выбросит весь код assert(). В результате отладка получит полный функционал проверок assert(), а версия релиза получит дополнительную производительность и уменьшение размера кода.

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

#ifndef NDEBUG
  // При ошибке (если expr==false) произойдет зависание в бесконечном цикле:
  #define MYASSERT(expr) if (!(expr)) while(1)
#else
  // В конфигурации релиза код MYASSERT будет выброшен:
  #define MYASSERT(expr)
#endif

[Ссылки]

1. Assertion (software development) site:wikipedia.org.
2. What is the “assert” function? site:stackoverflow.com.
3. Assert. Что это? site:habrahabr.ru.