【问题标题】:Best way for mapping non-sequential values in C在 C 中映射非序列值的最佳方法
【发布时间】:2019-07-12 18:32:18
【问题描述】:

我有一个非常人为的错误代码系统(数百个非顺序值,C 语言),同样人为地转换为人类可读的错误消息。

我正在考虑重构它,但我不确定将值映射到其对应字符串的最佳方式。有小费吗?

错误代码是唯一的,不能用来组成新代码。

错误代码的构建使得每个代码都有一个 base 值,指定它属于哪个模块。警告和状态消息有偏移量:

#define SOME_MODULE_ERR_BASE    0x120000
#define OTHER_MODULE_ERR_BASE   0x130000

#define STATUS_OFFSET  1000
#define WARNING_OFFSET 2000

/* Error codes as defined as needed, sequentially from the base value */
#define SOME_MODULE_ERR_NOT_FOUND   (SOME_MODULE_ERR_BASE + 1)
#define SOME_MODULE_ERR_BAD_CRC     (SOME_MODULE_ERR_BASE + 2)
#define SOME_MODULE_STATUS_BUSY     (SOME_MODULE_ERR_BASE + STATUS_OFFSET + 1)
#define SOME_MODULE_WARNING_INCOMPLETE (SOME_MODULE_ERR_BASE + WARNING_OFFSET + 1)

#define OTHER_MODULE_ERR_BAD_CRC     (OTHER_MODULE_ERR_BASE + 1)
#define OTHER_MODULE_ERR_NOT_FOUND   (OTHER_MODULE_ERR_BASE + 2)
/*...*/

我们目前有代码来打印相应的错误字符串,但是转换(从错误代码到字符串)的过程非常奇怪,甚至添加新的错误代码也是一件苦差事。我想改进我们的日志系统,但是这个愚蠢的代码妨碍了我。通过重构错误消息打印,我将能够重构日志系统。

基本上,我想要这个:

result = SomeModuleFunc();
printf("SomeModuleFunc returned %s\n", ErrToString(result));

它会酌情打印SOME_MODULE_ERR_NOT_FOUNDSOME_MODULE_ERR_BAD_CRCSOME_MODULE_STATUS_BUSY等。

在我看来,最简单的方法就是构建一个巨大的 switch-case 语句指向适当的字符串,但也许只是因为我想不出一个好的数据结构来简化映射过程。

【问题讨论】:

  • 键值对的二叉搜索树就可以了。
  • 错误报告性能重要吗?我认为一些 switch-case 实现会为你构建一个跳转表,并通过循环而不是内联测试来缩短代码。
  • 错误码只是整数?喜欢enum errors_e { ERROR_1 = 123, ERROR_2 = 43, ERROR_3 = 103958, }?有序列吗?喜欢enum { ERROR_X_1 = 1, ERROR_X_2, ERROR_X_3, ERROR_Y_1 =100, ERROR_Y_2 , ERROR_Y_3 } 吗?错误代码可以由多个错误组成吗?喜欢return ERROR_X_3 | ERROR_Y_1
  • @EugeneSh. 命题的变体:您可以对包含成对错误代码和相应字符串值的数组进行二进制搜索。
  • 仅仅因为错误值是非顺序的,并不意味着您不能使用具有直接代码到索引对应关系的简单表。如果某些条目没有消息也没关系。这里的主要潜在问题是,如果错误编号之间存在 间隔,那么表格会浪费大量空间。

标签: c algorithm data-structures error-handling


【解决方案1】:

除非错误代码很大或者您的资源非常有限,否则我的解决方案是只使用大量指向消息的 char 指针。

const char *error_msg[] = {
    "", "", "Out of memory", "", "Out of disk space", 
    "", "", "", "Unauthorized user" ... 
};

这很简单,而且很有效。如果最高代码非常高,这似乎是您的情况,您可能会遇到问题。在这种情况下,请使用指向指针的指针。

const char **error_msg;

void init_error() 
{
    error_msg = calloc(size, sizeof(*error_msg));
    error_msg[2] = "Out of memory";
    error_msg[4] = "Out of disk space";
    error_msg[8] = "Unauthorized user";
}

使用后一种方法,您无法在全局空间中进行初始化,因此请使用 init 函数并在main 的开头调用它。但是对于这两个你都可以使用这个函数,只要error_msg是全局的。

const char *ErrToString(size_t code) 
{ 
    return error_msg[code]; 
}

当然,这种方法会浪费内存,但除非您的资源非常有限,否则这不是问题。基数0x130000 在十进制中约为 120 万。所以如果指针是 8 个字节,这将低于 10MB,这在现代计算机上是没有的。它绝对比任何散列或二进制搜索快得多。通常,当您必须生成错误消息时,生成错误消息的性能并不是最大的问题,但如果您关心这一点,则值得了解。

优点:

  • 实施极其简单
  • 快速点亮

缺点:

  • 浪费内存

简单往往是最好的。不要把事情复杂化。

【讨论】:

    【解决方案2】:

    您可以使用某种哈希表。 如果你的 errcode-set 是固定的,你可以选择一些“magic divider”,给它划分错误码,并用提醒作为数组中的索引,例如:

    const char *p_msg = err_msg_arr[err_code % MAGIC];
    

    当然,你的数组必须有大小 [MAGIC];

    要找出这个魔法值,只需编写一个简单的程序,其中包含代码列表,然后尝试 MAGIC 的增量候选,直到找到一组唯一的遗骸。

    此类程序和输入数据文件的示例:

    #!/usr/local/bin/perl -w
    
    my %in_set;
    
    while(<>) {
      chomp;
      my ($errcode, $str) = m/(\d+)\s+(.+)/;
      $in_set{$errcode} = $str;
      #  print STDERR "added: [$errcode] => [$str]\n";
    }
    
    my $size = scalar keys %in_set;
    
    my @rems;
    for(my $magic = $size; $magic < $size * 10; $magic++) {
      @rems = (-1)x$magic;
      foreach (keys %in_set) {
        my $rem = $_ % $magic;
        goto NXT if $rems[$rem] >= 0;
        $rems[$rem] = $_;
      }
      # found the unque set of remains
      print "const char *err_msg[$magic] = {\n";
      for(my $m = 0; $m < $magic; $m++) {
        print $rems[$m] < 0? "\tNULL,\n" : "\t\"$in_set{$rems[$m]}\",\n";
      }
      exit;
      NXT:
    } # for magic
    

    数据文件:

    $ cat err_samples.txt 
    100 error num 1
    250 err 2
    5000 err #1
    

    运行结果:

     ./errgen.pl err_samples.txt 
    const char *err_msg[8] = {
        "err #1",
        NULL,
        "err 2",
        NULL,
        "error num 1",
        NULL,
        NULL,
        NULL,
    

    【讨论】:

    • 您的MAGIC 号码通常会变成(max_error_code+1),此时您又回到了起点。
    【解决方案3】:

    处理此问题的最简单方法是创建一个包含错误代码和错误消息的结构,然后创建一个包含每个代码/消息的数组,其中错误代码按数字递增的顺序排列:

    struct error_codes {
        int code;
        const char *message;
    };
    
    struct error_codes codes[] = {
        { 4, "file not found" },
        { 10, "out of memory" },
        { 12, "can't connect" },
        ...
    };
    

    然后创建一个函数,对给定错误代码的列表进行二进制搜索:

    const char *error_message(int code)
    {
        int len = sizeof(codes) / sizeof(codes[0]);
        int start = 0, end = len - 1;
    
        while (start <= end) {
            int idx = start + ((end - start) / 2);
    
            if (codes[idx].code < code) {
                end = idx - 1;
            } else if (codes[idx].code > code) {
                start = idx + 1;
            } else {
                return codes[idx].message;
            }
        }
        return "unknown error code";            
    }
    

    使用几百个错误代码,这不会超过 10 次迭代。

    如果消息的总数有点接近最大值并且所有值都是非负数,您可以创建一个字符串数组,其索引是错误代码,并为未使用的错误代码放入一个虚拟值。但是,鉴于您拥有的高错误代码库,这意味着一个非常大的数组,这是不可行的。

    【讨论】:

    • 既然可以调用bsearch(),为什么还要编写自己的二分搜索?
    • 值得注意的是,列表应该按数值排序。
    猜你喜欢
    • 2010-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-12
    • 1970-01-01
    • 2019-12-13
    • 1970-01-01
    • 2013-04-14
    相关资源
    最近更新 更多