断言用于在程序中检查不应该发生的条件,帮助快速定位逻辑错误。


1. 运行时断言:assert

头文件

#include <cassert>   // C++
#include <assert.h>  // C

语法

assert(expression);
  • 如果 expressionfalse (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

重要规则

  1. 断言用于“不可能发生”的错误,而非用户输入等可预期的错误。
  2. 不要在断言中放有副作用的代码,例如 assert(++i > 0) —— 因为断言可能在 Release 编译时被移除。
  3. 断言通常放在函数开头,检查参数合法性(前置条件)。
  4. 断言只应在 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 静态断言

特性assertstatic_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 返回错误码或抛异常)
  • 资源分配失败(应检查 newmalloc 返回值)
  • 文件不存在、网络断开等外部环境错误

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. 常见陷阱

  1. 断言表达式包含赋值或函数调用
    assert(++i > 0) —— 在 Release 模式下 ++i 不会执行,导致行为差异。
  2. 混淆断言与错误处理
    断言用于“程序逻辑错了”,错误处理用于“外部环境不满足预期”。
  3. 忘记包含头文件
    assert 需要 <cassert>static_assert 不需要头文件。
  4. 在 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 的区别,复杂策略按项目规范执行。