Show Lecture.Assertions as a slide show.
CS253 Assertions
Assertion failure!
You’re out of here!
Overview
There are two sorts of assertions in C++:
They both come from <cassert>.
assert() is a preprocessor macro (😲) that is, essentially:
void assert(bool condition) {
if (!condition) {
cerr << "assertion failed: name-of-condition\n"
abort();
}
}
It’s a runtime condition. It has to be in executable code,
that is, anywhere you could say: cout << "hi";
Name a place that’s not in executable code.
- between functions, where you could declare a global variable
- in a class declaration, outside of methods
void delete_file(const std::string &fname) {
assert(!fname.empty());
remove(fname.c_str());
}
int main() {
delete_file("tempfile");
delete_file("");
}
a.out: c.cc:2: void delete_file(const string&): Assertion `!fname.empty()' failed.
SIGABRT: Aborted
assert() is a macro, not a function.
assert() must be a macro, created with #define trickery,
in order to get the condition into the error message. Macros don’t
play by namespace rules, so std::assert
does not exist:
assert(3 > 2);
std::assert(4 < 5); // 🦡
In file included from /usr/local/gcc/11.2.0/include/c++/11.2.0/cassert:44,
from /usr/local/gcc/11.2.0/include/c++/11.2.0/x86_64-pc-linux-gnu/bits/stdc++.h:33,
from /s/bach/a/class/cs000/public_html/pmwiki/cookbook/c++-includes.h:7,
from <command-line>:
c.cc:2: error: expected unqualified-id before ‘(’ token
In general, macros are evil, and should be replaced with constexpr for
values, or simple functions. However, sometimes, you need a macro to do
the dirty work.
- Do not use assert() for dealing with user errors.
- Only use it for “impossible” circumstances, where the programmer
blundered:
- Ensure that a function argument, representing a size, is positive.
- Ensure that a pointer argument isn’t null.
- Ensure that the value was found in the table.
- Ensure that the two iterators refer to the same container.
- etc.
Disabling assertions
If you’re concerned about the run-time cost of assertions:
- You’re just fretting without data. Knock it off.
- The preprocessor symbol
NDEBUG
, which stands for
No DEBUGging, disables assertions, when defined.
- Use
#define NDEBUG
or g++ -DNDEBUG
to define NDEBUG
.
- Make sure that your assertions have no side effects.
Your code must produce the same results even if the assertions
are all disabled.
Avoid side effects
The assert() expression must not have side effects:
% cat ~cs253/Example/assert.cc
#include <iostream>
#include <cassert>
using namespace std;
int main() {
int n = 42;
assert(++n > 10); // 🦡
cout << n << '\n';
}
% g++ -Wall ~cs253/Example/assert.cc
% ./a.out
43
% g++ -DNDEBUG -Wall ~cs253/Example/assert.cc
% ./a.out
42
static_assert() is like assert(), but:
- It’s a compile-time test. It “executes” at compile time.
- It has zero run-time cost, because … compile-time.
- It fails like a syntax error fails.
- It doesn’t have to be in executable code.
- might be in a class definition
- might be outside of any function or class
- It takes two arguments:
- a condition
- an error message string (optional starting in C++17)
static_assert(-1 >> 1 == -1, "right shift must preserve sign");
int main() {
cout << "Hello, world!\n";
static_assert(sizeof(short)==2, "short must be 16 bits");
return 0;
}
static_assert(sizeof(int)==3, "int must be 24 bits");
c.cc:9: error: static assertion failed: int must be 24 bits
- static_assert() succeeds (silently) or fails (noisily)
at compile-time.
- ∴ the message must be a string literal.
You can’t build a message at run-time.
int value = 42;
static_assert(sizeof(int)==8, "must be exactly " + to_string(value));
c.cc:2: error: expected ‘)’ before ‘+’ token
Not even a C++ string literal:
static_assert(sizeof(int)==8, "must be exactly 8"s);
c.cc:1: internal compiler error: Segmentation fault
0xd23c5f crash_signal
../../gcc-11.2.0/gcc/toplev.c:327
0x83eb18 finish_static_assert(tree_node*, tree_node*, unsigned int, bool, bool)
../../gcc-11.2.0/gcc/cp/semantics.c:10134
0x7bc67a cp_parser_static_assert
../../gcc-11.2.0/gcc/cp/parser.c:15461
0x7c7c0d cp_parser_declaration_statement
../../gcc-11.2.0/gcc/cp/parser.c:13599
0x7c81bb cp_parser_statement
../../gcc-11.2.0/gcc/cp/parser.c:11829
0x7c9aad cp_parser_statement_seq_opt
../../gcc-11.2.0/gcc/cp/parser.c:12196
0x7c9b60 cp_parser_compound_statement
../../gcc-11.2.0/gcc/cp/parser.c:12145
0x7e5b9a cp_parser_function_body
../../gcc-11.2.0/gcc/cp/parser.c:24089
0x7e5b9a cp_parser_ctor_initializer_opt_and_function_body
../../gcc-11.2.0/gcc/cp/parser.c:24140
0x7e6d4a cp_parser_function_definition_after_declarator
../../gcc-11.2.0/gcc/cp/parser.c:30085
0x7e7eac cp_parser_function_definition_from_specifiers_and_declarator
../../gcc-11.2.0/gcc/cp/parser.c:30001
0x7e7eac cp_parser_init_declarator
../../gcc-11.2.0/gcc/cp/parser.c:21664
0x7c5da4 cp_parser_simple_declaration
../../gcc-11.2.0/gcc/cp/parser.c:14464
0x7efef5 cp_parser_declaration
../../gcc-11.2.0/gcc/cp/parser.c:14161
0x7f0bce cp_parser_toplevel_declaration
../../gcc-11.2.0/gcc/cp/parser.c:14190
0x7f0bce cp_parser_translation_unit
../../gcc-11.2.0/gcc/cp/parser.c:4942
0x7f0bce c_parse_file()
../../gcc-11.2.0/gcc/cp/parser.c:45321
0x8c162d c_common_parse_file()
../../gcc-11.2.0/gcc/c-family/c-opts.c:1218
Please submit a full bug report,
with preprocessed source if appropriate.
Please include the complete backtrace with any bug report.
See <https://gcc.gnu.org/bugs/> for instructions.
static_assert(sizeof(bool)==7, "I need 56-bit booleans!");
c.cc:1: error: static assertion failed: I need 56-bit booleans!
In C++ 2017, the message in static_assert() became optional. Sometimes,
it’s just not worth the trouble of writing the verbose error message,
and it’s good enough to have static_assert() show the file name and line
number so the programmer can look it up themself. It’s a tradeoff
between good error messages and development time.
static_assert(sizeof(bool)==7);
c.cc:1: error: static assertion failed