Assert. Что это?
Assert — это специальная конструкция, позволяющая проверять предположения о значениях произвольных данных в произвольном месте программы. Эта конструкция может автоматически сигнализировать при обнаружении некорректных данных, что обычно приводит к аварийному завершению программы с указанием места обнаружения некорректных данных. Странная, на первый взгляд, конструкция — может завалить программу в самый неподходящий момент. Какой же в ней смысл? Давайте подумаем, что произойдет, если во время исполнения программы в какой-то момент времени некоторые данные программы стали некорректными и мы не «завалили» сразу же программу, а продолжили ее работу, как ни в чем не бывало. Программа может еще долго работать после этого без каких-либо видимых ошибок. А может в любой момент времени в будущем «завалиться» сама по известной только ей причине. Или накачать вам полный винчестер контента с гей-порносайтов. Это называется неопределенное поведение (undefined behavior) и, вопреки расхожему мнению, оно свойственно не только языкам программирования с произвольным доступом к памяти (aka C, C++). Т.к. assert завершает программу сразу же после обнаружения некорректных данных, он позволяет быстро локализировать и исправить баги в программе, которые привели к некорректным данным. Это его основное назначение. Assert’ы доступны во многих языках программирования, включая java, c#, c и python.
Какие виды assert’ов бывают?
Assert’ы позволяют отлавливать ошибки в программах на этапе компиляции либо во время исполнения. Проверки на этапе компиляции не так важны — в большинстве случаев их можно заменить аналогичными проверками во время исполнения программы. Иными словами, assert’ы на этапе компиляции являются ничем иным, как синтаксическим сахаром. Поэтому в дальнейшем под assert’ами будем подразумевать лишь проверки во время исполнения программы.
- Проверка входящих аргументов в начале функции.
Важно понимать, что входящие аргументы функции могут быть неявными. Например, при вызове метода класса в функцию неявно передается указатель на объект данного класса (aka this и self). Также функция может обращаться к данным, объявленным в глобальной области видимости, либо к данным из области видимости лексического замыкания. Эти аргументы тоже желательно проверять с помощью assert’ов при входе в функцию.
Если некорректные данные обнаружены на этом этапе, то код данной функции может содержать баги. Пример:
- Проверка данных, с которыми работает функция, внутри кода функции.
Когда и где стоит использовать assert’ы?
Ответ прост — используйте assert’ы всегда и везде, где они хоть чуточку могут показаться полезными. Ведь они существенно упрощают локализацию багов в коде. Даже проверка результатов выполнения очевидного кода может оказаться полезной при последующем рефакторинге, после которого код может стать не настолько очевидным и в него может запросто закрасться баг. Не бойтесь, что большое количество assert’ов ухудшит ясность кода и замедлит выполнение вашей программы. Assert’ы визуально выделяются из общего кода и несут важную информацию о предположениях, на основе которых работает данный код. Правильно расставленные assert’ы способны заменить большинство комментариев в коде. Большинство языков программирования поддерживают отключение assert’ов либо на этапе компиляции, либо во время выполнения программы, так что они оказывают минимальное влияние на производительность программы. Обычно assert’ы оставляют включенными во время разработки и тестирования программ, но отключают в релиз-версиях программ. Если программа написана в лучших традициях ООП, либо с помощью enterprise методологии, то assert’ы вообще можно не отключать — производительность вряд ли изменится.
Когда можно обойтись без assert’ов?
Понятно, что дублирование assert’ов через каждую строчку кода не сильно улучшит эффективность отлова багов. Не существует единого мнения насчет оптимального количества assert’ов, также как и насчет оптимального количество комментариев в программе. Когда я только узнал про существование assert’ов, мои программы стали содержать 100500 assert’ов, многие из которых многократно дублировали друг друга. С течением времени количество assert’ов в моем коде стало уменьшаться. Следующие правила позволили многократно уменьшить количество assert’ов в моих программах без существенного ухудшения в эффективности отлова багов:
Можно избегать дублирующих проверок входящих аргументов путем размещения их лишь в функциях, непосредственно работающих с данным аргументом. Т.е. если функция foo() не работает с аргументом, а лишь передает его в функцию bar(), то можно опустить проверку этого аргумента в функции foo(), т.к. она продублирована проверкой аргумента в функции bar().
Можно опускать assert’ы на недопустимые значения, которые гарантированно приводят к краху программы в непосредственной близости от данных assert’ов, т.е. если по краху программы можно быстро определить местонахождение бага. К таким assert’ам можно отнести проверки указателя на NULL перед его разыменованием и проверки на нулевое значение делителя перед делением. Еще раз повторюсь — такие проверки можно опускать лишь тогда, когда среда исполнения гарантирует крах программы в данных случаях.
Вполне возможно, что существуют и другие способы, позволяющие уменьшить количество assert’ов без ухудшения эффективности отлова багов. Если вы в курсе этих способов, делитесь ими в комментариях к данному посту.
Когда нельзя использовать assert’ы?
Т.к. assert’ы могут быть удалены на этапе компиляции либо во время исполнения программы, они не должны менять поведение программы. Если в результате удаления assert’а поведение программы может измениться, то это явный признак неправильного использования assert’а. Таким образом, внутри assert’а нельзя вызывать функции, изменяющие состояние программы либо внешнего окружения программы. Например, следующий код неправильно использует assert’ы:
Очевидно, что данные могут оказаться незащищенными при отключенных assert’ах.
Чтобы исправить эту ошибку, нужно сохранять результат выполнения функции во временной переменной, после чего использовать эту переменную внутри assert’а:
Т.к. основное назначение assert’ов — отлов багов (aka ошибки программирования), то они не могут заменить обработку ожидаемых ошибок, которые не являются ошибками программирования. Например:
Если write() возвращает 0, то это вовсе не означает, что в нашей программе есть баг. Если assert’ы в программе будут отключены, то ошибка записи может остаться незамеченной, что впоследствие может привести к печальным результатам. Поэтому assert() тут не подходит. Тут лучше подходит обычная обработка ошибки. Например:
Я программирую на javascript. В нем нет assert’ов. Что мне делать?
В некоторых языках программирования отсутствует явная поддержка assert’ов. При желании они легко могут быть там реализованы, следуя следующему «паттерну проектирования»:
Вообще, assert’ы обычно реализованы в различных фреймворках и библиотеках, предназначенных для автоматизированного тестирования. Иногда они там называются expect’ами. Между автоматизированным тестированием и применением assert’ов есть много общего — обе техники предназначены для быстрого выявления и исправления багов в программах. Но, несмотря на общие черты, автоматизированное тестирование и assert’ы являются не взаимоисключающими, а, скорее всего, взаимодополняющими друг друга. Грамотно расставленные assert’ы упрощают автоматизированное тестирование кода, т.к. тестирующая программа может опустить проверки, дублирующие assert’ы в коде программы. Такие проверки обычно составляют существенную долю всех проверок в тестирующей программе.
What is the "assert" function?
I’ve been studying OpenCV tutorials and came across the assert function; what does it do?
9 Answers 9
assert will terminate the program (usually with a message quoting the assert statement) if its argument turns out to be false. It’s commonly used during debugging to make the program fail more obviously if an unexpected condition occurs.
You can also add a more informative message to be displayed if it fails like so:
Or else like this:
When you’re doing a release (non-debug) build, you can also remove the overhead of evaluating assert statements by defining the NDEBUG macro, usually with a compiler switch. The corollary of this is that your program should never rely on the assert macro running.
From the combination of the program calling abort() and not being guaranteed to do anything, asserts should only be used to test things that the developer has assumed rather than, for example, the user entering a number rather than a letter (which should be handled by other means).
The assert computer statement is analogous to the statement make sure in English.
Many compilers offer an assert() macro. The assert() macro returns TRUE if its parameter evaluates TRUE and takes some kind of action if it evaluates FALSE. Many compilers will abort the program on an assert() that fails; others will throw an exception
One powerful feature of the assert() macro is that the preprocessor collapses it into no code at all if DEBUG is not defined. It is a great help during development, and when the final product ships there is no performance penalty nor increase in the size of the executable version of the program.
The assert() function can diagnose program bugs. In C, it is defined in <assert.h> , and in C++ it is defined in <cassert> . Its prototype is
The argument expression can be anything you want to test—a variable or any C expression. If expression evaluates to TRUE, assert() does nothing. If expression evaluates to FALSE, assert() displays an error message on stderr and aborts program execution.
How do you use assert()? It is most frequently used to track down program bugs (which are distinct from compilation errors). A bug doesn’t prevent a program from compiling, but it causes it to give incorrect results or to run improperly (locking up, for example). For instance, a financial-analysis program you’re writing might occasionally give incorrect answers. You suspect that the problem is caused by the variable interest_rate taking on a negative value, which should never happen. To check this, place the statement
assert(interest_rate >= 0); at locations in the program where interest_rate is used. If the variable ever does become negative, the assert() macro alerts you. You can then examine the relevant code to locate the cause of the problem.
To see how assert() works, run the sample program below. If you enter a nonzero value, the program displays the value and terminates normally. If you enter zero, the assert() macro forces abnormal program termination. The exact error message you see will depend on your compiler, but here’s a typical example:
Assertion failed: x, file list19_3.c, line 13 Note that, in order for assert() to work, your program must be compiled in debug mode. Refer to your compiler documentation for information on enabling debug mode (as explained in a moment). When you later compile the final version in release mode, the assert() macros are disabled.
Enter an integer value: 10
Enter an integer value: -1
Error Message: Abnormal program termination
Your error message might differ, depending on your system and compiler, but the general idea is the same.
assert
The definition of the macro assert depends on another macro, NDEBUG , which is not defined by the standard library.
If NDEBUG is defined as a macro name at the point in the source code where <cassert> or <assert.h> is included, then assert does nothing.
If NDEBUG is not defined, then assert checks if its argument (which must have scalar type) compares equal to zero. If it does, assert outputs implementation-specific diagnostic information on the standard error output and calls std::abort . The diagnostic information is required to include the text of expression , as well as the values of the predefined variable __func__ and (since C++11) the predefined macros __FILE__ and __LINE__ .
The expression assert ( E ) is guaranteed to be a constant subexpression, if either
- NDEBUG is defined at the point where assert is last defined or redefined (i.e., where the header <cassert> or <assert.h> was last included); or
- E , contextually converted to bool , is a constant subexpression that evaluates to true .
Contents
[edit] Parameters
| condition | — | expression of scalar type |
[edit] Return value
[edit] Notes
Because assert is a function-like macro, commas anywhere in condition that are not protected by parentheses are interpreted as macro argument separators. Such commas are often found in template argument lists and list-initialization:
There is no standardized interface to add an additional message to assert errors. A portable way to include one is to use a comma operator provided it has not been overloaded, or use && with a string literal:
The implementation of assert in Microsoft CRT does not conform to C++11 and later revisions, because its underlying function ( _wassert ) takes neither __func__ nor an equivalent replacement.
C Language Assertion
An assertion is a predicate that the presented condition must be true at the moment the assertion is encountered by the software. Most common are simple assertions, which are validated at execution time. However, static assertions are checked at compile time.
Syntax
- assert(expression)
- static_assert(expression, message)
- _Static_assert(expression, message)
Parameters
| Parameter | Details |
|---|---|
| expression | expression of scalar type. |
| message | string literal to be included in the diagnostic message. |
Remarks
Both assert and static_assert are macros defined in assert.h .
The definition of assert depends on the macro NDEBUG which is not defined by the standard library. If NDEBUG is defined, assert is a no-op:
Opinion varies about whether NDEBUG should always be used for production compilations.
- The pro-camp argues that assert calls abort and assertion messages are not helpful for end users, so the result is not helpful to user. If you have fatal conditions to check in production code you should use ordinary if/else conditions and exit or quick_exit to end the program. In contrast to abort , these allow the program to do some cleanup (via functions registered with atexit or at_quick_exit ).
- The con-camp argues that assert calls should never fire in production code, but if they do, the condition that is checked means there is something dramatically wrong and the program will misbehave worse if execution continues. Therefore, it is better to have the assertions active in production code because if they fire, hell has already broken loose.
- Another option is to use a home-brew system of assertions which always perform the check but handle errors differently between development (where abort is appropriate) and production (where an ‘unexpected internal error — please contact Technical Support’ may be more appropriate).
static_assert expands to _Static_assert which is a keyword. The condition is checked at compile time, thus condition must be a constant expression. There is no need for this to be handled differently between development and production.
Precondition and Postcondition
One use case for assertion is precondition and postcondition. This can be very useful to maintain invariant and design by contract. For a example a length is always zero or positive so this function must return a zero or positive value.
Simple Assertion
An assertion is a statement used to assert that a fact must be true when that line of code is reached. Assertions are useful for ensuring that expected conditions are met. When the condition passed to an assertion is true, there is no action. The behavior on false conditions depends on compiler flags. When assertions are enabled, a false input causes an immediate program halt. When they are disabled, no action is taken. It is common practice to enable assertions in internal and debug builds, and disable them in release builds, though assertions are often enabled in release. (Whether termination is better or worse than errors depends on the program.) Assertions should be used only to catch internal programming errors, which usually means being passed bad parameters.
Possible output with NDEBUG undefined:
Possible output with NDEBUG defined:
It’s good practice to define NDEBUG globally, so that you can easily compile your code with all assertions either on or off. An easy way to do this is define NDEBUG as an option to the compiler, or define it in a shared configuration header (e.g. config.h ).
Static Assertion
Static assertions are used to check if a condition is true when the code is compiled. If it isn’t, the compiler is required to issue an error message and stop the compiling process.
A static assertion is one that is checked at compile time, not run time. The condition must be a constant expression, and if false will result in a compiler error. The first argument, the condition that is checked, must be a constant expression, and the second a string literal.
Unlike assert, _Static_assert is a keyword. A convenience macro static_assert is defined in <assert.h> .
Prior to C11, there was no direct support for static assertions. However, in C99, static assertions could be emulated with macros that would trigger a compilation failure if the compile time condition was false. Unlike _Static_assert , the second parameter needs to be a proper token name so that a variable name can be created with it. If the assertion fails, the variable name is seen in the compiler error, since that variable was used in a syntactically incorrect array declaration.
Before C99, you could not declare variables at arbitrary locations in a block, so you would have to be extremely cautious about using this macro, ensuring that it only appears where a variable declaration would be valid.
Assertion of Unreachable Code
During development, when certain code paths must be prevented from the reach of control flow, you may use assert(0) to indicate that such a condition is erroneous:
Whenever the argument of the assert() macro evaluates false, the macro will write diagnostic information to the standard error stream and then abort the program. This information includes the file and line number of the assert() statement and can be very helpful in debugging. Asserts can be disabled by defining the macro NDEBUG .
Another way to terminate a program when an error occurs are with the standard library functions exit , quick_exit or abort . exit and quick_exit take an argument that can be passed back to your environment. abort() (and thus assert ) can be a really severe termination of your program, and certain cleanups that would otherwise be performed at the end of the execution, may not be performed.
The primary advantage of assert() is that it automatically prints debugging information. Calling abort() has the advantage that it cannot be disabled like an assert, but it may not cause any debugging information to be displayed. In some situations, using both constructs together may be beneficial:
When asserts are enabled, the assert() call will print debug information and terminate the program. Execution never reaches the abort() call. When asserts are disabled, the assert() call does nothing and abort() is called. This ensures that the program always terminates for this error condition; enabling and disabling asserts only effects whether or not debug output is printed.
You should never leave such an assert in production code, because the debug information is not helpful for end users and because abort is generally a much too severe termination that inhibit cleanup handlers that are installed for exit or quick_exit to run.
Assert Error Messages
A trick exists that can display an error message along with an assertion. Normally, you would write code like this
If the assertion failed, an error message would resemble
Assertion failed: p != NULL, file main.c, line 5
However, you can use logical AND ( && ) to give an error message as well
Now, if the assertion fails, an error message will read something like this
Assertion failed: p != NULL && "function f: p cannot be NULL", file main.c, line 5
The reason as to why this works is that a string literal always evaluates to non-zero (true). Adding && 1 to a Boolean expression has no effect. Thus, adding && "error message" has no effect either, except that the compiler will display the entire expression that failed.