V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
monkeyNik
V2EX  ›  分享创造

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

  •  
  •   monkeyNik ·
    Water-Melon · 2023-01-14 23:43:23 +08:00 · 514 次点击
    这是一个创建于 683 天前的主题,其中的信息可能已经有所发展或是发生改变。

    本文将介绍一种可以拥有更多含义的 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 库中提供了多种多样和奇奇怪怪的组件,有些组件常用,但也包含了一些额外扩展功能。

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3388 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 11:47 · PVG 19:47 · LAX 03:47 · JFK 06:47
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.