C 语言预定义宏与 assert
更新时间:2026-08-26。本文是
languages/c/主题入门层第 59 篇。写完代码,你希望程序出问题时自己告诉你:在哪一行、哪个函数、什么条件不成立,而不是默默算错。C 给了两件顺手工具:预定义宏(__FILE__、__LINE__、__func__)报告位置,assert强制检查"不可能出错"的假设。
本文要回答的问题
- C 有哪些常用预定义宏?怎么用?
assert是什么?什么时候用、什么时候别用?- 断言失败和错误处理是一回事吗?
一、预定义宏:编译器自带的"位置传感器"
| 宏 | 含义 | 例子 |
|---|---|---|
__FILE__ | 当前源文件名(字符串) | "main.c" |
__LINE__ | 当前行号(整数) | 42 |
__func__ | 当前函数名(C99,字符串) | "main" |
__DATE__ | 编译日期 | "Aug 26 2026" |
__TIME__ | 编译时间 | "14:30:00" |
__STDC__ | 是否标准 C 编译器 | 1 |
c
#include <stdio.h>
int divide(int a, int b) {
if (b == 0) {
fprintf(stderr, "%s:%d in %s: divide by zero\n",
__FILE__, __LINE__, __func__);
return -1;
}
return a / b;
}日志自带文件名、行号、函数名——出错时不用猜是哪一行。
二、assert:断言"这不可能错"
c
#include <assert.h>
int arr_sum(const int *arr, int n) {
assert(arr != NULL); // 调用方不该传 NULL
assert(n > 0); // 数组长度必须为正
int sum = 0;
for (int i = 0; i < n; i++)
sum += arr[i];
return sum;
}assert(expr) 的行为:
expr为真 → 什么都不做,继续执行;expr为假 → 打印:文件名、行号、函数名、表达式,然后abort()终止程序。
a.out: arr_sum.c:5: arr_sum: Assertion `n > 0' failed.
Aborted (core dumped)断言是写给"不可能发生"的情况:检查的是"我的代码假设",不是用户的输入。
三、发布时自动禁用
assert 在定义 NDEBUG 时整体消失:
bash
gcc -DNDEBUG main.c -o app # 发布编译加这个选项| 模式 | 定义 NDEBUG? | assert 行为 |
|---|---|---|
| 调试版 | 否 | 全面检查 |
| 发布版 | 是 | 不编译,零开销 |
这意味着:assert 里的表达式不能被"依赖"——比如 assert(++count > 0) 这种带副作用的断言,发布版一编译,副作用也没了。断言只做纯检查。
四、断言 vs 错误处理
两者不是一回事,别混用:
| 场景 | 用 assert | 用 if/错误处理 |
|---|---|---|
| 检查"代码假设"(arr 非空、n>0) | ✅ | |
| 处理用户输入(文件不存在、网络超时) | ✅ | |
| 程序内部状态一致(链表没环) | ✅ | |
| 外部资源失败(malloc 失败) | ✅(判空返回) |
经验法则:能由外部输入/环境导致的情况,用错误处理(程序要继续活下去);只可能因代码 bug 导致的,用断言(尽早暴露、立刻崩溃)。
五、调试日志最佳实践
预定义宏 + 宏定义组合成"日志开关":
c
#define LOG_ERROR(fmt, ...) \
fprintf(stderr, "[ERROR] %s:%d %s: " fmt "\n", \
__FILE__, __LINE__, __func__, ##__VA_ARGS__)
LOG_ERROR("connection to %s failed", host);发布时想保留错误日志、关掉调试日志,用条件编译拆两层:
c
#ifdef DEBUG
#define LOG_DEBUG(fmt, ...) /* 打印 */
#else
#define LOG_DEBUG(fmt, ...) ((void)0) // 什么都不做
#endif六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 断言带副作用 | 发布版行为变化 | 断言只写纯检查 |
| 用 assert 处理用户输入 | 用户一输错程序崩溃 | 改用 if 错误处理 |
| 发布版忘加 -DNDEBUG | 断言拖慢性能 | 构建脚本统一加 |
| 断言表达式太重 | 热路径开销 | 发布禁用/避免复杂断言 |
| 只信 assert 不测 | 掩盖真实 bug | 断言是补充非替代 |
七、与本站主线衔接
- 宏定义与 ## 拼接技巧,见宏定义 #define;
- 用 assert 排查空指针崩溃,见NULL 解引用与段错误;
- 崩溃转储与调试器分析,见gdb 调试入门。
一句话总结
预定义宏(__FILE__/__LINE__/__func__)让日志自带"定位信息";assert 检查"不可能错"的代码假设、失败即打印位置并 abort、发布时 -DNDEBUG 自动消失——记住分工:外部问题走错误处理,代码 bug 交给断言,让程序出事时自己开口说话。