断言用于在程序中检查不应该发生的条件,帮助快速定位逻辑错误。
1. 运行时断言:assert
头文件
#include <cassert> // C++
#include <assert.h> // C语法
assert(expression);- 如果
expression为 false (0),程序调用abort()终止,并输出错误信息(文件名、行号、表达式)。 - 如果为 true (非0),什么也不做,继续执行。
示例
#include <cassert>
#include <iostream>
int divide(int a, int b) {
assert(b != 0); // b 绝不能为 0
return a / b;
}
int main() {
divide(10, 2); // 正常
divide(10, 0); // 触发断言,程序终止
return 0;
}输出类似:
Assertion failed: b != 0, file test.cpp, line 4
重要规则
- 断言用于“不可能发生”的错误,而非用户输入等可预期的错误。
- 不要在断言中放有副作用的代码,例如
assert(++i > 0)—— 因为断言可能在 Release 编译时被移除。 - 断言通常放在函数开头,检查参数合法性(前置条件)。
- 断言只应在 Debug 模式下启用,Release 模式下应关闭。
关闭断言(Release 模式)
定义宏 NDEBUG 可以禁用所有 assert:
#define NDEBUG // 放在 #include <cassert> 之前
#include <cassert>或在编译时定义:g++ -DNDEBUG test.cpp
2. 静态断言:static_assert (C++11 起)
头文件 不需要头文件,是 C++ 关键字。
语法
static_assert(constant_expression, "message");constant_expression必须是编译时可计算的常量表达式。- 如果为
false,编译器报错并显示message。 - 如果为
true,什么也不做(无运行时开销)。
C++17 改进
可以省略 message 参数(但某些编译器仍建议提供):
static_assert(sizeof(int) == 4); // C++17 起允许无消息示例
#include <type_traits>
template <typename T>
void process(T value) {
static_assert(std::is_integral_v<T>, "T must be integral type");
// 编译时确保 T 是整数类型
}
int main() {
process(10); // OK
process(3.14); // 编译错误:T must be integral type
return 0;
}常见用途
- 检查类型大小:
static_assert(sizeof(void*) == 8, "Not 64-bit platform"); - 检查类型特性:
static_assert(std::is_pod<MyStruct>::value, "MyStruct must be POD"); - 检查数组长度、枚举范围等编译期常量条件。
3. 运行时断言 vs 静态断言
| 特性 | assert | static_assert |
|---|---|---|
| 检查时机 | 运行时 | 编译时 |
| 表达式类型 | 可以是变量、函数调用 | 必须是常量表达式 |
| 开销 | Debug 下有检查,Release 可移除 | 零运行时开销 |
| 适用场景 | 参数合法性、状态一致性 | 模板参数、平台假设、类型属性 |
| 是否可禁用 | 是(NDEBUG) | 否(永远检查) |
4. 最佳实践
使用 assert 的场景
- 检查函数前置条件(参数不能为
nullptr、除数为 0) - 检查后置条件(函数返回值应在合理范围)
- 检查内部状态一致性(例如链表节点计数)
void push_back(Node* node) {
assert(node != nullptr);
assert(node->next == nullptr);
// ...
}使用 static_assert 的场景
- 模板参数约束(C++20 概念之前常用)
- 跨平台编译时确保假设成立
- 检查结构体大小、对齐等底层布局
static_assert(sizeof(FileHeader) == 64, "FileHeader size mismatch");不要用断言的情况
- 用户输入验证(应用常规错误处理,如
if返回错误码或抛异常) - 资源分配失败(应检查
new或malloc返回值) - 文件不存在、网络断开等外部环境错误
5. 完整示例(开箱即用)
#include <cassert>
#include <iostream>
// 编译时检查平台
static_assert(sizeof(int) == 4, "This code assumes 32-bit int");
// 运行时检查参数
int safe_divide(int a, int b) {
assert(b != 0 && "division by zero");
return a / b;
}
int main() {
std::cout << safe_divide(100, 5) << std::endl; // 20
// safe_divide(100, 0); // 触发断言
return 0;
}6. 常见陷阱
- 断言表达式包含赋值或函数调用:
assert(++i > 0)—— 在 Release 模式下++i不会执行,导致行为差异。 - 混淆断言与错误处理:
断言用于“程序逻辑错了”,错误处理用于“外部环境不满足预期”。 - 忘记包含头文件:
assert需要<cassert>,static_assert不需要头文件。 - 在 C 语言中使用
static_assert:
C11 提供了_Static_assert,用法类似,但需包含<assert.h>或使用static_assert宏(C11 兼容)。
7. C 语言中的静态断言(C11)
#include <assert.h>
_Static_assert(sizeof(int) == 4, "int must be 4 bytes");
// C11 也可使用 static_assert 宏(需要 #include <assert.h>)
static_assert(sizeof(double) == 8, "double must be 8 bytes");总结速查
| 需求 | 使用 |
|---|---|
| 检查运行时参数 | assert(expr); |
| 检查编译时类型/大小 | static_assert(expr, "msg"); |
| Release 关闭 assert | 定义 NDEBUG |
| 断言无副作用 | ✅ 不要放赋值、函数调用 |
学习定位:现代 C++ 高阶知识。断言用于把“不应该发生”的条件尽早暴露出来;日常开发应知道运行期断言和 static_assert 的区别,复杂策略按项目规范执行。 |