Appearance
C 标准库 - <assert.h>
概述
<assert.h> 头文件定义了 assert() 宏,用于在程序运行时验证假设条件是否成立。如果条件为假(即表达式求值为 0),程序会打印诊断信息并终止执行。
assert 宏
函数原型
c
void assert(scalar expression);功能
assert() 宏在调试阶段非常有用,它可以:
- 验证程序中的假设条件
- 在条件不满足时提供详细的错误信息
- 帮助快速定位和修复程序中的错误
工作原理
当 assert(expression) 被调用时:
- 如果
expression的值为真(非零),程序继续执行 - 如果
expression的值为假(零),程序会:- 打印诊断信息到标准错误流(stderr)
- 调用
abort()函数终止程序
诊断信息通常包括:
- 源文件名
- 行号
- 失败的表达式
基本用法
简单示例
c
#include <stdio.h>
#include <assert.h>
int main() {
int x = 10;
assert(x > 0);
printf("x = %d\n", x);
x = -5;
assert(x > 0); // 这会触发断言失败
printf("这行不会执行\n");
return 0;
}输出(当断言失败时):
x = 10
Assertion failed: x > 0, file test.c, line 10数组边界检查
c
#include <stdio.h>
#include <assert.h>
#define ARRAY_SIZE 10
int get_element(int arr[], int index) {
assert(index >= 0 && index < ARRAY_SIZE);
return arr[index];
}
int main() {
int numbers[ARRAY_SIZE] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
printf("numbers[5] = %d\n", get_element(numbers, 5));
printf("numbers[15] = %d\n", get_element(numbers, 15)); // 断言失败
return 0;
}指针检查
c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
void process_data(int *data) {
assert(data != NULL);
printf("处理数据: %d\n", *data);
}
int main() {
int value = 42;
process_data(&value);
process_data(NULL); // 断言失败
return 0;
}禁用断言
使用 NDEBUG 宏
在发布版本中,通常需要禁用断言以提高性能。可以通过定义 NDEBUG 宏来实现:
c
#define NDEBUG
#include <assert.h>
int main() {
int x = -5;
assert(x > 0); // 这个断言不会被执行
printf("程序继续执行\n");
return 0;
}编译时定义 NDEBUG
使用编译器选项定义 NDEBUG:
bash
gcc -DNDEBUG program.c -o program条件编译
c
#ifdef DEBUG
#include <assert.h>
#define ASSERT(expr) assert(expr)
#else
#define ASSERT(expr) ((void)0)
#endif
int main() {
int x = -5;
ASSERT(x > 0); // 在 DEBUG 模式下会检查
printf("程序继续执行\n");
return 0;
}实际应用场景
1. 函数参数验证
c
#include <assert.h>
#include <string.h>
char* safe_strcpy(char* dest, const char* src, size_t dest_size) {
assert(dest != NULL);
assert(src != NULL);
assert(dest_size > 0);
strncpy(dest, src, dest_size - 1);
dest[dest_size - 1] = '\0';
return dest;
}2. 不变量检查
c
#include <assert.h>
typedef struct {
int* data;
size_t size;
size_t capacity;
} Vector;
void vector_push_back(Vector* vec, int value) {
assert(vec != NULL);
assert(vec->size <= vec->capacity);
if (vec->size == vec->capacity) {
vec->capacity *= 2;
vec->data = realloc(vec->data, vec->capacity * sizeof(int));
assert(vec->data != NULL);
}
vec->data[vec->size++] = value;
assert(vec->size <= vec->capacity);
}3. 状态验证
c
#include <assert.h>
typedef enum {
STATE_IDLE,
STATE_RUNNING,
STATE_PAUSED,
STATE_STOPPED
} State;
typedef struct {
State current_state;
int counter;
} Machine;
void machine_start(Machine* m) {
assert(m != NULL);
assert(m->current_state == STATE_IDLE);
m->current_state = STATE_RUNNING;
m->counter = 0;
}
void machine_pause(Machine* m) {
assert(m != NULL);
assert(m->current_state == STATE_RUNNING);
m->current_state = STATE_PAUSED;
}自定义断言处理
自定义断言宏
c
#include <stdio.h>
#include <stdlib.h>
#define CUSTOM_ASSERT(expr, message) \
do { \
if (!(expr)) { \
fprintf(stderr, "断言失败: %s\n", message); \
fprintf(stderr, "文件: %s, 行: %d\n", __FILE__, __LINE__); \
abort(); \
} \
} while (0)
int main() {
int x = -5;
CUSTOM_ASSERT(x > 0, "x 必须大于 0");
return 0;
}带日志的断言
c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define LOG_ASSERT(expr) \
do { \
if (!(expr)) { \
time_t now = time(NULL); \
fprintf(stderr, "[%s] 断言失败: %s\n", \
ctime(&now), #expr); \
fprintf(stderr, "文件: %s, 行: %d\n", __FILE__, __LINE__); \
abort(); \
} \
} while (0)
int main() {
int x = -5;
LOG_ASSERT(x > 0);
return 0;
}断言的最佳实践
1. 只用于调试
断言主要用于开发和调试阶段,不应该用于处理运行时错误:
c
// 好的做法
void* allocate_memory(size_t size) {
void* ptr = malloc(size);
assert(ptr != NULL); // 调试时检查
return ptr;
}
// 更好的做法(生产环境)
void* safe_allocate_memory(size_t size) {
void* ptr = malloc(size);
if (ptr == NULL) {
fprintf(stderr, "内存分配失败\n");
exit(EXIT_FAILURE);
}
return ptr;
}2. 避免副作用
断言表达式不应该有副作用:
c
// 不好的做法
int i = 0;
assert(i++ > 0); // 副作用:i 会增加
// 好的做法
int i = 0;
assert(i > 0); // 无副作用
i++;3. 提供有意义的条件
断言条件应该清晰表达程序的假设:
c
// 不好的做法
assert(flag);
// 好的做法
assert(is_valid_state(state));
assert(ptr != NULL && *ptr > 0);4. 不要用于用户输入验证
断言不应该用于验证用户输入:
c
// 不好的做法
void process_user_input(int value) {
assert(value >= 0 && value <= 100); // 用户输入不应该用断言
}
// 好的做法
void process_user_input(int value) {
if (value < 0 || value > 100) {
fprintf(stderr, "输入值必须在 0 到 100 之间\n");
return;
}
}常见错误
1. 在发布版本中依赖断言
c
// 危险的做法
int divide(int a, int b) {
assert(b != 0); // 发布版本中这个检查会被禁用
return a / b;
}
// 安全的做法
int divide(int a, int b) {
if (b == 0) {
fprintf(stderr, "除零错误\n");
return 0;
}
return a / b;
}2. 使用复杂的表达式
c
// 不好的做法
assert((ptr != NULL) && (*ptr > 0) && (ptr[1] < 100));
// 好的做法
assert(ptr != NULL);
assert(*ptr > 0);
assert(ptr[1] < 100);总结
<assert.h> 提供的 assert() 宏是一个强大的调试工具,它可以帮助开发者:
- 快速发现和定位程序中的错误
- 文档化程序的假设和不变量
- 提高代码的可靠性
正确使用断言可以显著提高代码质量,但需要注意:
- 断言主要用于调试,不应该用于处理运行时错误
- 发布版本中应该禁用断言
- 避免在断言表达式中使用有副作用的代码
- 为断言提供清晰、有意义的条件表达式