assert
定义
assert是写在程序某一点上的断言语句,其参数是一个布尔表达式,含义是程序运行到该处时该表达式应当恒为真。常见的写法是在语句后接一个条件,例如在 C 语言中写作 assert(x > 0),位于其后的代码即可依赖这一条件成立。[1]在 C 语言的实现中,assert 由标准头文件 assert.h 定义为宏;C++ 的 assert.h 与 cassert 也提供同名宏。Java、Python 等语言则把 assert 作为语言层面的关键字或语句。[3][4]更一般地说,断言是描述程序运行中某个状态性质的逻辑公式,被置于程序文本的特定位置,作为程序与其规格之间的桥梁。[5]
原理
以 C 语言为例,assert 宏展开为对一个标量表达式的检查:表达式为真时不起作用,为假时把出错的实参文本、源文件名、行号以及所在函数名输出到标准错误流,随后调用 abort() 终止程序。[2]glibc 的实现中,未定义 NDEBUG 时 assert 展开为三元表达式,仅在条件为假时调用内部函数报告位置信息;定义了 NDEBUG 后该宏被定义为空操作,不产生任何代码。这种开关设计使断言可以在编译期被整体移除。[6]Java 的 assert 语句在条件为假时抛出 AssertionError,并支持 assert 表达式1 : 表达式2 的形式,把表达式2 的值作为详细信息附在错误中;断言默认关闭,需要运行时启用,关闭时该语句在语义上等同于空语句。[7]Python 的 assert 语句等价于在 debug 为真时判断条件,条件为假则抛出 AssertionError;以优化选项启动解释器时,代码生成器不会为 assert 生成任何字节码。[8]断言的检查时机不限于运行期。编译期检查的断言称为静态断言,C11 与 C++11 通过 static_assert 直接支持:其常量表达式在编译时求值,为非零时不产生代码且无运行开销,为零时编译失败并输出指定的字符串信息。[9][10]
发展历程
把逻辑断言写入程序以论证正确性的思想可以追溯到 20 世纪 40 年代末:冯·诺伊曼与戈尔茨坦 1947 年的论文、图灵 1949 年的《检查一个大型程序》都引入了类似的思路,但当时并未进入计算机科学的公共视野。1967 年,罗伯特·弗洛伊德在《给程序赋予含义》中系统提出归纳断言方法;1969 年,托尼·霍尔(C. A. R. Hoare)在《计算机程序的公理基础》一文中将其形式化,提出后来被称为霍尔三元组({p} S {q})的系统,用前断言和后断言刻画语句执行的输入输出行为。[11]中文文献对这段脉络的记述与此一致:弗洛伊德于 1967 年提出归纳断言方法,霍尔于 1969 年将其形式化并提出程序验证的公理系统,此后陆续出现基于归纳断言的自动与半自动程序验证系统。[12]1976 年,戴维·马图谢克在《断言语句之理由》一文中主张把 assert <布尔表达式> 作为一条简单、透明的语句引入类 ALGOL 语言,并列举了它记录假设、表达不变式、可在不影响程序行为的前提下增删、可通过编译选项按注释处理等优点,这为断言从验证理论走入工程实践提供了论证。[13]此后,断言逐步成为语言与库的内置设施:C 通过 assert.h 提供宏,Java 在 1.4 版本引入 assert 语句,Python 将其作为语句提供,C11 与 C++11 标准化了 static_assert,C23 与 C++17 起该关键字允许省略第二个字符串实参。[3][4][14]
应用
断言在开发周期中主要用于暴露逻辑错误:程序员在假定必然成立的条件下放置断言,一旦失败即立刻得到失败位置与现场信息,便于定位问题,许多实现还会随即终止程序以免错误状态扩散。[3]按断言所处位置,可分为前置条件、后置条件与类不变式等类别:前置条件描述调用前的合法状态,后置条件描述成功完成后应满足的状态,类不变式描述每个实例都应保持的性质,因而断言可支撑一种非正式的契约式设计风格。[4]在循环中,断言还用于书写循环不变式,即每次迭代前后都成立的命题,用以论证循环能达到预期结果。[15]静态断言常见于模板元编程与底层语言中,用于在编译阶段校验类型大小、结构体字段偏移、枚举长度等常量性质,例如以 static_assert(offsetof(struct timer, DATA) == 4, ...) 的形式固定数据布局。[9][3]断言也被用于测试驱动开发与规范检验:断言构造使语言本身即可承载这类检查,无需引入第三方库。[3]静态分析工具亦会建模 assert 的行为,把失败的断言视为运行期错误,从而在程序执行前发现可能触发断言失败的路径。[16]
局限
断言并非运行期错误处理手段。C 语言的安全编码规范指出,assert 应只用于防范程序员自身假设出错,不应用于检查非法用户输入、文件打开失败、网络错误、内存耗尽、系统资源耗尽、系统调用失败或权限不足等运行期状况;这些情况需要在发布版本中依然生效的处理逻辑,而断言通常在上线前即被关闭。[2]Java 的文档同样规定,断言不应用于公有方法的参数检查,也不应用于执行程序正常运作所必需的副作用,因为断言可能被禁用,公有方法无论断言是否开启都必须校验参数并抛出约定类型的异常。[7]断言表达式中的副作用会带来风险:若表达式带副作用的求值在关闭断言后不再发生,程序行为就会随断言开关而改变,因此标准断言中的表达式不应产生副作用。Java 在早期的 Dalvik 虚拟机上默认忽略 assert,需要通过系统属性或命令行参数显式开启。在服务器程序与嵌入式系统的部署环境中,断言失败可能被外部输入触发,从而造成服务拒绝,此时写入日志并拒绝请求之类的软失败方式更为合适。[2]此外,断言的存在与否并不等价于代码正确:缺少断言并不说明程序有错,断言只是发现并消除缺陷的辅助诊断工具。[2]断言还有其表达能力边界:Java 的 assert 构造并非完整的契约式设计设施,只提供有限的布尔检查;把断言用于承诺编译器某条边界条件不可达时,关闭断言反而可能降低性能。[4][3]
参见
参考资料
- epfl.ch 上的网页 [引用日期2026-10-29]
- viewpage [引用日期2026-10-29]
- Assertion (software development) [引用日期2026-10-29]
- Programming With Assertions [引用日期2026-10-29]
- Assertions: A Personal Perspective [引用日期2026-10-29]
- C and C++ linkage orwhat does extern "C" means? [引用日期2026-10-29]
- アサーションを使用したプログラミング [引用日期2026-10-29]
- python.org 上的网页 [引用日期2026-10-29]
- viewpage [引用日期2026-10-29]
- static-assert-c [引用日期2026-10-29]
- references [引用日期2026-10-29]
- [科普中国]-程序验证 - 版权归原作者所有,如有侵权,请联系我们 [引用日期2026-10-29]
- JavaScript disabled - S I G P L A N Notices 36 1976 August [引用日期2026-10-29]
- assert.h (GNU Gnulib) [引用日期2026-10-29]
- CS 2110 Code Style Guidelines [引用日期2026-10-29]
- User assertion - assert statement fails [引用日期2026-10-29]