Linux系统C/C++通用错误码实现模板

背景

公司C++项目初期是安排不同的人编写不同的模块,有嵌入式ARM的,有socket协议的,有mysql的,有redis的,不同人风格不同。由于当时我还在运维小组搞docker,没参与规则的制定,后来接手时,开始确定编码规范,也建立了Git管理(Git尝试过sub modules,不过太过繁琐,最终弃用了),由于历史原因,后面要不断重构代码。

错误码大型项目中都会使用到,不同模块虽然有不同的错误返回值,但还是有必要建立一套基本的错误码使用系统,让大家都遵守。否则,错误码随便定义、使用,重构起来十分麻烦。
本文是在重构项目错误码之前进行的自测,与项目正式使用的代码有些许不同,但本质上是一致的。参考Linux内核和系统调用,确定了一些基本的通用类错误码。至于与业务有关的,则自由定制,对外形式、接口保持不同。

代码

闲话不提,直接上代码。

头文件

头文件my_errorcode.h如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* @file my_errorcode.h
* @author Late Lee
* @date 2018.9.1
*
* @brief
* 通用错误码定义及实现
*
* @note
* 1、错误码从0开始计算,顺序添加。
* 2、实际使用时,除E_OK外,错误码可以是负数(需要手工添加'-'),也可以是正数。
* 3、错误码可以是任何数字,但不在枚举之列则显示未知错误码。
*
* @log
*
*/
#ifndef _MY_ERRORCODE_H
#define _MY_ERRORCODE_H

enum errorcode{
E_OK = 0, ///< 成功
E_FAIL, ///< 一般性错误
E_INNER, ///< 内部错误(一般在同一个模块内使用,不对外公开
E_POINTER, ///< 非法指针
E_INVALARG, ///< 非法参数
E_NOTIMPL = 5, ///< 功能未实现
E_OUTOFMEM, ///< 内存申请失败/内存不足
E_BUFERROR, ///< 缓冲区错误(不足,错乱)
E_PERM, ///< 权限不足,访问未授权的对象
E_TIMEOUT, ///< 超时
E_NOTINIT = 10, ///< 未初始化
E_INITFAIL, ///< 初始化失败
E_ALREADY, ///< 已初始化,已经在运行
E_INPROGRESS, ///< 已在运行、进行状态
E_EXIST, ///< 申请资源对象(如文件或目录)已存在
E_NOTEXIST, ///< 资源对象(如文件或目录)、命令、设备等不存在
E_BUSY, ///< 设备或资源忙(资源不可用)
E_FULL, ///< 设备/资源已满
E_EMPTY, ///< 对象/内存/内容为空
E_OPENFAIL, ///< 资源对象(如文件或目录、socket)打开失败
E_READFAIL, ///< 资源对象(如文件或目录、socket)读取、接收失败
E_WRITEFAIL, ///< 资源对象(如文件或目录、socket)写入、发送失败
E_DELFAIL, ///< 资源对象(如文件或目录、socket)删除、关闭失败
E_CODECFAIL, ///< 加解密、编码解密失败
E_CRC_FAIL, ///< crc校验错误
E_TOOMANY, ///< 消息、缓冲区、内容太多
E_TOOSMALL, ///< 消息、缓冲区、内容太少
E_NETNOTREACH, ///< 网络不可达(无路由,网关错误)
E_NETDOWN, ///< 网络不可用(断网)

// more...

E_END, ///< 占位,无实际作用
};

const char* get_errorcode(int ec);
#endif

实现文件

my_errorcode.c文件内容,注意,使用2种方法实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include "my_errorcode.h"

#if 01
///////////// 推荐方式
static const char* faults[] = {
[E_OK] = "Success",
[E_FAIL] = "General Failed",
[E_INNER] = "Internal error",
[E_POINTER] = "Invalid Pointer",
[E_INVALARG] = "Invalid argument",
[E_NOTIMPL] = "Not implemented",
[E_OUTOFMEM] = "Out of memory",
[E_BUFERROR] = "Buffer error",
[E_PERM] = "Permission denied",
[E_TIMEOUT] = "Timed out",
[E_NOTINIT] = "Object not init",
[E_INITFAIL] = "Object init failed",
[E_ALREADY] = "Operation already in progress",
[E_INPROGRESS] = "Operation now in progress",
[E_EXIST] = "Object exist",
[E_NOTEXIST] = "Object not exist",
[E_BUSY] = "Device or resource busy",
[E_FULL] = "Device or resource full",
[E_EMPTY] = "Device or resource empty",
[E_OPENFAIL] = "Device or resource open failed",
[E_READFAIL] = "Device or resource read failed",
[E_WRITEFAIL] = "Device or resource write failed",
[E_DELFAIL] = "Device or resource delete failed",
[E_CODECFAIL] = "Encode or decode failed",
[E_CRC_FAIL] = "CRC failed",
[E_TOOMANY] = "Object too many",
[E_TOOSMALL] = "Object too small",
[E_NETNOTREACH] = "Network is unreachable",
[E_NETDOWN] = "Network is down",


// more...
};

const char* get_errorcode(int ec)
{
if (ec < 0) ec = -ec;
printf("ec: %d\n", ec);
if (ec < E_OK || ec >= E_END) return "Unknown error code";
return faults[ec];
}

////////////////////////// 另一种方式实现
#else

typedef struct {
int ec;
const char *str;
} errorstring;

static const errorstring faults[] = {
{E_OK, "Success"},
{E_FAIL, "General Failed"},
{E_INNER, "Internal error"},
{E_POINTER, "Invalid Pointer"},
{E_INVALARG, "Invalid argument"},
{E_NOTIMPL, "Not implemented"},
{E_OUTOFMEM, "Out of memory"},
{E_BUFERROR, "Buffer error"},
{E_PERM, "Permission denied"},
{E_TIMEOUT, "Timed out"},
{E_NOTINIT, "Object not init"},
{E_INITFAIL, "Object init failed"},
{E_ALREADY, "Operation already in progress"},
{E_INPROGRESS, "Operation now in progress"},
{E_EXIST, "Object exist"},
{E_NOTEXIST, "Object not exist"},
{E_BUSY, "Device or resource busy"},
{E_FULL, "Device or resource full"},
{E_EMPTY, "Device or resource empty"},
{E_OPENFAIL, "Device or resource open failed"},
{E_READFAIL, "Device or resource read failed"},
{E_WRITEFAIL, "Device or resource write failed"},
{E_DELFAIL, "Device or resource delete failed"},
{E_CODECFAIL, "Encode or decode failed"},
{E_CRC_FAIL, "CRC failed"},
{E_TOOMANY, "Object too many"},
{E_TOOSMALL, "Object too small"},
{E_NETNOTREACH, "Network is unreachable"},
{E_NETDOWN, "Network is down"},
};


const char* get_errorcode(int ec)
{
if (ec < 0) ec = -ec;

for (unsigned int i = 0; i < sizeof(faults)/sizeof(faults[0]); i++)
if (faults[i].ec == ec)
return faults[i].str;

return "Error code unknown";
}
#endif

测试代码

main.c文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
打印结果:
Success
Error code unknown
-6(Out of memory)
9(Timed out)
*/

#include <stdlib.h>
#include <stdio.h>

#include "my_errorcode.h"

int foo()
{
return -E_OUTOFMEM;
}

int bar()
{
return E_TIMEOUT;
}

int main(void)
{
printf("%s\n", get_errorcode(0));
printf("%s\n", get_errorcode(-250));

int ret = foo();
printf("%d(%s)\n", ret, get_errorcode(ret));

ret = bar();
printf("%d(%s)\n", ret, get_errorcode(ret));

return 0;
}

设计理念

错误码使用枚举类型定义,方便后续的添加,注意,枚举类型默认是累积1的。
另外,如果不想这样设计,也可以使用:

1
#define E_OK                   0   ///< 成功

这种宏定义。

如果想看到具体的错误码数值,则要在枚举后面手动添加数字。
错误码0表示成功,其它值表示失败,一般使用负数表示,本文所示代码,在实际返回时需要添加负号-,如return -E_OUTOFMEM;。当然,如果不要这么麻烦,就直接返回,那么其值就是正数。但是,为了兼容,也为了枚举类型的定义,获取错误码信息时需要将负数转成正数。

至于获取错误码信息get_errorcode的实现,则使用了GNU C编译器的扩展语法,从代码编写看十分简洁,不过,这个文件必须使用c语法,即gcc编译,g++编译会失败。

小结

通过上面的实现,可以得到一套基本满足应用的错误码模板。本文仅定义通用的错误码,根据需求添加业务相关错误码即可。

李迟 2018.9.1 深夜,与博导、蔡总、刘总夜宵回来