C 语言错误输出及日志
develop c
不要重复造轮子。
– 佚名
错误表述
C 语言没有异常机制。
使用异常或状态
别的编程语言的函数类似 y = f(x), 如果运行会出错就抛出一个异常。
C 很多本来可以没有返回值的函数有返回值 status = f(x, &y):
status 为 0 表示没有出错。
- 内核空间
-
status == 0代表没有出错,status < 0代表出错,-status代表出错类型
-
- 用户空间
-
status == 0代表没有出错,status > 0代表出错。status代表出错类型 -
status > 0代表没有出错,status == -1代表出错。- 通过设置
errno来表示出错类型。用strerr(errno),,perror(NULL)查看报错(单线程,多线程errno是共享的不行)。 - 可以用非负数表示成功的某种状态(比如成功写入了多少个字符)。
- 通过设置
-
此类函数有:
-
close(): -1 或 0 -
fclose(): -1 或 0 -
write(): -1 或 成功的状态 -
fwrite(): -1 或 成功的状态 -
read(): -1 或 成功的状态 -
fread(): -1 或 成功的状态 -
fstat(): -1 或 0 -
ioctl(): -1 或 0
使用哨兵值
比如有的编程语言 y = f(x) ,如果 y 为 None 就是出错,否则就是正确的结果。
C 中可以使用 y = f(x) 其中:
- 如果
y类型是指针,则哨兵值是NULL - 如果
y是int,则哨兵值为 -1 。为此 C 还特意typedef了ssize_t来代表有哨兵值的size_t。
此类函数有:
-
fopen():NULL或指针 -
open(): -1 或文件描述符fd -
mmap(): -1 的指针 或地址 -
malloc():NULL或 指针
一般地(笔者观察的规律):
-
y是指针的会用NULL做哨兵值 -
y是个结构体会用状态。 -
y是个非负整数(例如fd)会用 -1 做哨兵值
错误处理
无封装
因为 strerr() 会返回所有 errno 的错误信息,所以可以:
#if 0
bin="$(basename "$0")" && bin="${bin%%.*}" && cc "$0" -o"$bin" && exec ./"$bin" "$@"
#endif
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FILE_NAME "non_existent_file"
int main(int argc, char *argv[]) {
FILE *fp = fopen(FILE_NAME, "r");
if (fp == NULL) {
fprintf(stderr, "%s: %s: %s\n", argv[0], FILE_NAME, strerror(errno));
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
$ chmod +x main.c
$ ./main.c
./main: non_existent_file: No such file or directory
这 fprintf() 明显可以封装一下。
第一次封装
stdio.h 提供封装好的 perror() 。
#if 0
bin="$(basename "$0")" && bin="${bin%%.*}" && cc "$0" -o"$bin" && exec ./"$bin" "$@"
#endif
#include <stdio.h>
#include <stdlib.h>
#define FILE_NAME "doesn't_exist_file"
int main(int argc, char *argv[]) {
FILE *fp = fopen(FILE_NAME, "r");
if (fp == NULL) {
fprintf(stderr, "%s: ", argv[0]);
perror(FILE_NAME);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
第二次封装
如果不需要退出可以使用 perror() 。如果需要退出可以直接用 err() 。
#if 0
bin="$(basename "$0")" && bin="${bin%%.*}" && cc "$0" -o"$bin" && exec ./"$bin" "$@"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <err.h>
#define FILE_NAME "doesn't_exist_file"
int main(int argc, char *argv[]) {
FILE *fp = fopen(FILE_NAME, "r");
if (fp == NULL)
err(EXIT_FAILURE, FILE_NAME);
return EXIT_SUCCESS;
}
不过需要注意 err.h 是 POSIX C 的头文件不是 ANSI C 的。
日志
我们的更多需求:
- 每次
perror()的信息能否保存下来以供日后查看?最好保存的时候再记录一些信息,比如时间戳、进程的 PID 等等。 - 每次
fprintf()的信息能否设定一个等级:调试、信息、通知、警告、错误、致命错误等等?超过某个等级的信息才会被显示?
内核空间
linux/module.h 中的 printk("XXX") 的输入的第一个字符(不可见字符)用来表达等级 。如果第一个字符不是不可见字符,则使用默认的等级。
所以使用的时候经常 printk(LEVEL "XXX") 。
用户空间
POSIX C 的 syslog.h 中的 syslog(LEVEL, "XXX") 。默认不打印输出结果。日志的输出结果需要通过一个 syslogd 的守护进程记录。常见的 syslogd 包括:
-
journalctl:systemd自带。journalctl -fn0 -tprogram_name。使用和systemd一样的环境变量SYSTEMD_XXX。例如SYSTEMD_COLORS=1可以强制输出颜色无论输出是否是终端。 syslogd-ng
![]() |
![]() |
![]() |


