为什么在libc中滥用宏是可以的?

3

如果我告诉你我想要实现以下代码:

static const uint8_t jump_table[] =
{
    /* ' ' */  1,            0,            0, /* '#' */  4,
               0, /* '%' */ 14,            0, /* '\''*/  6,
      ...
      ...
    /* 't' */ 27, /* 'u' */ 16,            0,            0,
    /* 'x' */ 18,            0, /* 'z' */ 13
};

#define LABEL(Name) do_##Name
#define NOT_IN_JUMP_RANGE(Ch) ((Ch) < ' ' || (Ch) > 'z')
#define CHAR_CLASS(Ch) (jump_table[(INT_T) (Ch) - ' '])
#define REF(Name) &&do_##Name
#define JUMP(ChExpr, table)                   \
do                                            \
{                                             \
    const void *ptr;                          \
    spec = (ChExpr);                          \
    ptr = NOT_IN_JUMP_RANGE (spec) ? REF (form_unknown) \
       : table[CHAR_CLASS (spec)];            \
    goto *ptr;                                \
} while (0)

#define TABLE                                 \
/* Step 0: at the beginning.  */              \
static JUMP_TABLE_TYPE step0_jumps[30] =      \
{                                             \
    REF (form_unknown),                       \
    REF (flag_space),       /* for ' ' */     \
    REF (flag_plus),        /* for '+' */     \
    REF (flag_minus),       /* for '-' */     \
     ...                                      \
    REF (flag_i18n),        /* for 'I' */     \
};

使用示例如下:
void usage_example_function(void)
{
    do
    {
      TABLE;

      /* Get current character in format string.  */
      JUMP (*++f, step0_jumps);

      /* ' ' flag.  */
      LABEL (flag_space):
      space = 1;
      JUMP (*++f, step0_jumps);
      ...
      ...
    } while (something)
}

您会告诉我,在任何合理的编程风格标准下,这是不可接受的,提交这样的代码很可能会带来更多的伤害而不是好处。
现在,glibc使用远比这个(上面代码摘自那里)更多的宏滥用来实现vfprintf,然而这段代码是如此多的编译程序的一部分,并被测试了那么多次,以至于让我觉得今天的(宏)编码标准缺乏正当性。
为什么这样的宏滥用是错误的?或者,如果这样的滥用是如此错误,为什么我们接受这样的libc实现?

我不确定,但我猜你可以给出好的建议,即使你自己并不遵循它。 “吸烟对你身体不好!” 妈妈点着一支香烟对孩子说。 - domsson
2
“滥用宏是不好的”这个想法肯定是有来源的。正是像这样的代码让我们认识到了这一点。 - Angew is no longer proud of SO
但是这段代码实际上是对这些认识的反例,因为它是一段经过如此多次测试的代码,以至于你几乎可以说它是没有漏洞的。 - izac89
2
一个典型的“说一套做一套”的例子...故事的寓意是轻率的,其他C库都经过高度优化以提高速度(内核也是如此)。它们也经过了高度的同行评审和测试。我们编写的代码应该清晰易读,避免微观优化,让编译器进行优化。(你不必维护glibc,但在职业环境中,某些人可能需要维护你编写的代码)(另一个有趣的例子是strlen或qsort,如果我没记错,其中甚至涉及到魔数)。 - David C. Rankin
1
我不同意你的反例想法。这段代码几乎没有漏洞,因为很多人使用它,即使是最小的漏洞也会很快被发现。也就是说,它并不是因为编写方式而无漏洞,而是尽管编写方式有问题,仍然没有漏洞。 - user3386109
你可以尝试制作一个“更好”的实现。如果你这样做了:如果它不起作用,请在此处发布你的代码(或在代码审查中),顺便说一句:一旦你理解了它的工作原理,它是一种相当优雅的构建状态机的方式(在元级别上)。 - joop
1个回答

2
  1. 样式规则是旨在提高软件工程师之间(包括当前团队中的工程师,将来将在代码上工作的工程师以及自己未来的自己)之间的沟通,并减少错误并使软件开发更加高效的指南。这些指南应该平衡其他目标,例如性能、必要性、解决特定情况下出现的问题等。

  2. 与编译器和语言实现相关的库中的软件通常被称为服务于特殊目的。它可能是C实现的一部分,并且可能需要与C编译器协调。因此,它不完全受到严格符合C代码的约束-它可能使用特定于C实现的结构或功能。(这种使用应该有明确的文档记录。)通常,不可能用严格符合C代码编写C实现-C实现必须以未由C标准指定的方式与硬件和操作系统软件交互。即使在可能的情况下,出于性能和其他目标的原因,可能也不是理想的选择。

  3. 库提供的一个目的是做所有“肮脏”的工作,以便您不必自己做,或者做难或复杂的工作。旨在供许多人使用的库是一个很好的投资工程努力的地方,因为少数人的工作会对许多人产生效益。考虑到glibc的直接和间接用户数量,投资回报的杠杆作用是巨大的,因此如果可以提高性能或可移植性,则编写一些奇怪的代码是值得的。在需要这样复杂的代码以获得所需结果或希望使用替代方法时,将该代码放入库中可能会减少世界上这种代码的数量,因为将其放入一个位置供许多人使用,然后不需要编写自己的类似代码来实现相同的目的。

  4. 问题中显示的代码与一些预处理器宏的使用相比较温和。除了GCC“标签作为值”扩展(使用&&来获取标签的地址)外,它使用标准功能和构造状态机,这应该对任何拥有软件工程学士学位的人都很熟悉。在研究了该代码之后,经验丰富的软件工程师可以认识到它正在做什么,并且可以相当轻松地使用该代码。


1
此外,这个实现是一种元编程形式:构建了一个微型语言,只有几个基本模块,可以用来以更抽象的方式构建(解析器),例如在这里使用状态机。此外:这些基本模块可以被重复使用和/或扩展,例如使用(几乎)相同的宏调用骨架来实现函数族的snprintf()分支。 - joop

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接