开源 C 语言库之错误码管理

2023-01-14 23:43:23 +08:00
 monkeyNik

本文将介绍一种可以拥有更多含义的 int 型错误码。

错误码演进

首先,我们先来说一说错误码的演进,顺便看一看本文中要讲述的错误码演进到了何种程度。

阶段 1. 区分一个函数的执行是成功还是失败

在这一阶段,人们使用 0 和非 0 值来表示函数的执行是成功或者失败。

但这时我们也会直观的想到,是否能在失败时给出失败原因。因此就衍生出了下一个阶段。

阶段 2. 获知一个函数的执行是成功还是失败,失败的话,要给出失败原因。

这一阶段,人们一般的做法是,函数返回 0 表示成功,失败则返回一个负数,而不同负数的绝对值用于指代错误类型。例如,-1 表示内存不足,-2 表示参数错误,-3 表示无权限等等。

但这一阶段的问题也很明显,就是:同一个错误码可能会在同一个函数中出现多次,我们如何得知当前的返回值是哪一个错误点抛出的?

阶段 3. 获知一个函数的执行是成功还是失败,失败的话,要给出失败原因和报错点位置。

在这一阶段,常规的处理方式基本上都是在出错点打印一段 log 信息,并返回一个负值给调用方。

然而这样做的弊端就是,返回值本身的定位能力并为加强,而是依赖于 log 组件来提供了位置信息,这样的方式增加了返回值与 log 的耦合度。

阶段 4. 获知一个函数的执行是成功还是失败,失败的话,在不依赖 log 的情况下,要给出失败原因和报错点位置。

这一阶段就是我们这篇文章主要介绍的内容了,请往下看。

先看一段示例代码

#include "mln_error.h"

#define OK    0 //0 是个特殊值,表示一切正常,由 0 生成的返回值就是 0 ,不会增加额外信息
#define NMEM  1 //由使用者自行定义,但顺序必须与 errs 数组给出的错误信息顺序一致

int main(void)
{
    char msg[1024];
    mln_string_t files[] = {
        mln_string("a.c"),
    };
    mln_string_t errs[] = {
        mln_string("Success"),
        mln_string("No memory"),
    };
    mln_error_init(files, errs, sizeof(files)/sizeof(mln_string_t), sizeof(errs)/sizeof(mln_string_t));
    printf("%x %d [%s]\n", RET(OK), CODE(RET(OK)), mln_error_string(RET(OK), msg, sizeof(msg)));
    printf("%x %d [%s]\n", RET(NMEM), CODE(RET(NMEM)), mln_error_string(RET(NMEM), msg, sizeof(msg)));
    printf("%x %d [%s]\n", RET(2), CODE(RET(2)), mln_error_string(RET(2), msg, sizeof(msg)));
    printf("%x %d [%s]\n", RET(0xff), CODE(RET(0xff)), mln_error_string(RET(0xff), msg, sizeof(msg)));
    return 0;
}

在这段代码中,笔者使用了Melon 库的错误码组件。我们先给出结论和效果,那就是利用 Melon 的错误码组件,我们可以在一个出错点处获取到一个整型数值。通过这个数值,我们可以知道如下信息:

  1. 是否出错
  2. 错误类型
  3. 出错文件
  4. 出错行号

这段代码的输出如下:

0 0 [Success]
ffffedff 1 [a.c:18:No memory]
ffffec01 255 [a.c:19:Unknown Code]
ffffeb01 255 [a.c:20:Unknown Code]

原理分析

原理其实很简单。首先在 Melon 中,通过调用 mln_error_init 函数来初始化工程全局的错误码管理变量。伴随着该函数调用的还有两个数组:

  1. 本工程包含的(或者说开发者关心的)源代码文件名。本例中是:files
  2. 错误码对应的错误信息字符串数组。本例中是:errs

而错误号则是错误信息字符串数组的下标,且第一个元素(下标为 0 )的错误信息含义应为成功无异常

然后,在错误点处,将错误码传递给 RET 宏来生成返回值。如果错误码大于 0 ,则会生成一个负数。这个负数的各部分含义如下(从左到右,比特位从高到低):

[1-bit 符号位| 9-bit 文件名下标| 14-bit 当前行号| 8-bit 错误码]

如此拼凑出整个 32-bit 的 int 型数值。

此外,对于未曾在初始化时给出的源文件路径和错误码,皆可生成错误码,只是因为不存在映射关系,因此在函数 mln_error_string 调用时返回的详细错误字符串内容中,对应的部分仅给出 Unkown xxx 的字样。

使用限制

因为使用了位域,因此每一个定位条件都有数量限制:

  1. 支持 255 个错误码( 0xff 为表留值)
  2. 支持每个文件 16383 行( 0x3ffff 为保留值)
  3. 支持 511 个文件( 0x1ff 为保留值)
  4. 仅针对文件名,而非文件路径名,因此应尽量避免不同目录下出现同名代码文件

感谢阅读,对文中 Melon 库感兴趣的读者可以访问 Github repo。Melon 库中提供了多种多样和奇奇怪怪的组件,有些组件常用,但也包含了一些额外扩展功能。

455 次点击
所在节点    分享创造
0 条回复

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/908972

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX