禁止用断言检测程序在运行期间可能导致的错误,可能发生的错误要用错误处理代码来处理

【描述】 断言主要用于调试期间,在编译Release版本时将其关闭。因此,断言应该用于防止不正确的程序员假设,而不能用在Release版本上检查程序运行过程中发生的错误。

断言永远不应用于验证是否存在运行时(与逻辑相对)错误,例如

  • 无效的用户输入(包括命令行参数和环境变量)

  • 文件错误(例如,打开,读取或写入文件时出错)

  • 网络错误(包括网络协议错误)

  • 内存不足的情况(例如,malloc()类似的故障)

  • 系统资源耗尽(例如,文件描述符,进程,线程)

  • 系统调用错误(例如,执行文件,锁定或解锁互斥锁时出错)

  • 无效的权限(例如,文件,内存,用户)

例如,防止缓冲区溢出的代码不能使用断言实现,因为该代码必须编译到Release版本的可执行文件中。 如果服务器程序在网运行时由恶意用户触发断言失败,会导致拒绝服务攻击。在这种情况下,更适合使用软故障模式,例如写入日志文件和拒绝请求。

【错误代码示例】 以下代码的所有ASSERT的用法是错误的。例如,错误的使用ASSERT宏来验证内存分配是否成功,因为内存的可用性取决于系统的整体状态,并且在程序运行的任何时候都可能耗尽,所以必须以具有韧性的方式来妥善处理并将程序从内存耗尽中恢复。因此,使用ASSERT宏来验证内存分配是否成功将是不合适的,因为这样做可能导致进程突然终止,从而开启了拒绝服务攻击的可能性。

FILE  *fp = fopen(path,  "r");
ASSERT(fp != NULL); // 错误用法:文件有可能打开失败
char  *str = (char *)malloc(MAX_LINE);
ASSERT(str != NULL); // 错误用法:内存有可能分配失败
ReadLine(fp, str);
char  *p = strstr(str, "age=");
ASSERT(p != NULL); // 错误用法:文件中不一定存在该字符串
char  *end = NULL;
long age = strtol(p + 4, &end, 10);
ASSERT(age > 0); // 错误用法:文件内容不一定符合预期

【正确代码示例】 下面代码演示了如何重构上面的错误代码:

FILE  *fp = fopen(path,  "r");
if (fp == NULL) {
	... // 错误处理
}

char  *str = (char *)malloc(MAX_LINE);
if (str == NULL) {
	... // 错误处理
}

read_line(fp, str);
char  *p = strstr(str,  "age=");
if (p == NULL) {
	... // 错误处理
}

char *end = NULL;

long age = strtol(p + 4, &end, 10);
if (age <= 0) {
	... // 错误处理
}

禁止在断言内改变运行环境

【描述】 在程序正式发布阶段,断言不会被编译进去,为了确保调试版和正式版的功能一致性,严禁在断言中使用任何赋值、修改变量、资源操作、内存申请等操作。

例如,以下的断言方式是错误的:

ASSERT(p1 = p2); // p1被修改
ASSERT(i++  > 1000); // i被修改
ASSERT(close(fd) == 0); // fd被关闭