目的
断言是非常重要的东西。 我之前写过一篇这方面的文章,终究感觉浅了些。 或许只有编写 C 语言、C++ 语言的人才能感觉到这一点。 如果只是用 Python、Java 这类语言编程,很难感觉到这一点。 记得我平生第一次遇到一个超大难题,就是因为内存溢出导致的,那个问题差点让我进了监狱(因上线失败,全省的一个功能不能用,我只能去监狱了);虽然最后,问题还是解决了:
回忆这些事,我想了很多,想到如何定位崩溃的问题,如何调试这种问题,甚至从汇编代码推测这些问题,然而,却没有想到一种更为高级的方式: 如何预防这种问题??? 如何预防呢?那就是断言:
什么是断言?生活中,它指对某事非常肯定地下结论或陈述;日常语言里就是"十分肯定地说"。 在编程中,断言就是必须成立的事情,否则程序就不能运行。 也可以这样说:这个条件必须为真;如果为假,说明有 bug,请立刻暴露。 这与平常的异常是不同的:异常是可以接受的,就如同...... 这个意思得弄清楚。 断言与异常的区别: 断言:例如空指针、状态机非法状态、内部不变量 size <= capacity、函数前置条件。 它一旦发生,说明我的代码逻辑坏了,或内部的契约被破坏,救不救都没意义,应该立刻暴露。 个人考虑:这里说代码逻辑坏了,过于绝对了,有可能加载的参数存在问题。因此,如何区分断言与异常,关键在于程序能不能自救:如果不能自救,就要用断言来处理。 异常/错误码:例如文件打不开、除数为 0 来自用户输入、网络超时、JSON 解析失败。 它一旦发生,是正常世界的一部分,程序要有策略地应对 。 
过程
先说说断言的含义
具体展开,有四层意义:
- 声明契约:把隐含前提变成显式代码
Q_ASSERT_X(index < vec.size(), ...)等于在说:"这个函数能正确工作,前提是下标必须小于长度。" 没有断言时,这个前提只存在于程序员脑子里;有了断言,它就成了代码的一部分,读代码的人、改代码的人都能看到。 - Fail Stop:在错误的第一现场停住 长度 3 却访问 4,若不断言,
vec[4]会读越界内存------可能读到垃圾值、可能崩在无关的地方、可能悄悄污染数据,事后极难定位。 断言让程序在越界发生的那一行 立刻中断,把"果"和"因"钉在一起:"下标 4 不满足 < 3,就是这里出的问题。" - 可执行的文档,支撑重构与测试
Q_ASSERT_X(index < vec.size(), ...)比注释强:注释会过期,断言不会------条件不成立就停。 重构时,断言帮你确认"这个假设是否还被调用者遵守";测试时,断言失败直接指出"哪个契约被破坏了"。 - 断言 vs 普通错误处理(关键区别) 断言失败不应该被 try/catch 或返回值优雅处理掉,因为: "用户输入了非法数字" → 用返回值/异常处理; "栈已满还 push""空栈取 top" → 是调用者违约 / 代码 bug,应立刻停住,去修代码。 所以断言的语义是:"这个条件必须为真;如果为假,说明有 bug,请立刻暴露。" 结合前面
vector长度 3 却访问 4 的例子,断言的意义可以收束成一句话: 断言是把程序员脑中的"这里必须成立"写成可执行的契约;一旦契约被破坏,说明代码逻辑已经错了,应当立刻停住、暴露在出错的现场,而不是带病继续跑。 一句话总结 断言的意义 = 把"必须满足的条件"写成代码,让 bug 在违反契约的第一现场立刻暴露,而不是靠运气在别处崩溃。 就弄个例子研究一下,就拿最典型的例子------数组超范围的情况。
Debug模式正常的时候:

Debug模式的异常的时候:
这时,无论点击重试,还是忽略,程序都会停止,如下: 
Release模式正常的时候:

Release模式异常的时候:

这说明在 Release 模式下,assert 不起作用,这是当前的情况。 根据现实情况,在 Release 里 assert 是需要起作用的,因为 Release 里程序更是运行在充满各种未知的环境,程序必须具备应对各种未知情况的能力。 未来,我感觉编码工具或许会做出改变。 在《程序员的修炼之道》这本书里,也明确说需要这个功能。 理论也需要在实践中修正。
自己写断言
自己写断言, 自己写的断言就可以在release和debug模式下,都可以运行:
cpp
#pragma once
// MyAssert.h
#pragma once
#include <QMessageBox>
#include <QDebug>
#include <cstdlib> // for abort()
// 无论 Debug 还是 Release,MY_ASSERT 都会生效
#define MY_ASSERT(condition) \
do { \
if (!(condition)) { \
QString msg = QString("断言失败!\n\n条件: %1\n文件: %2\n行号: %3") \
.arg(#condition) \
.arg(__FILE__) \
.arg(__LINE__); \
qCritical() << msg; \
QMessageBox::critical(nullptr, "程序断言失败", msg); \
abort(); /* 或者使用 std::terminate() 来终止程序 */ \
} \
} while (false)

总结
断言是什么,为什么起这个名字? 汉语意思是:经周密判断后作出明确结论。 在编程语言里的意思,就是不可能发生的事;如果这种事发生,程序就不能运行了,也就是程序不能容忍这种事情的发生。其实可以理解为,这是程序运行的必须条件。 就好像植物生长必须要有水和阳光一样,少了一样就不可生长,是不能忍受的;遇到这种情况,程序需要做的就是报错,之后退出运行。 就如下面所示: 
最后,还是那句话:编程中,断言就是程序运行必须的条件,否则程序就无法运行。 这是程序运行必须的条件。