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/include/c++/8/cassert:44,
from /usr/include/c++/8/x86_64-redhat-linux/bits/stdc++.h:33,
from c.cc:4:
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(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 code brevity.
static_assert(sizeof(bool)==7);
c.cc:1: error: static assertion failed