为什么在libc中可以滥用宏

时间:2018-12-18 09:05:59

标签: c libc

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

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实现?

1 个答案:

答案 0 :(得分:2)

  1. 样式规则是旨在改善软件工程师(包括团队中的当前工程师,将来将要使用代码的工程师以及自己将来的自己)之间的交流的指南,旨在减少错误并进行软件开发更高效。这些准则应与其他目标保持平衡,例如性能,必要性,解决在特定情况下出现的问题等。

  2. 与编译器和语言实现相关的库中的软件通常被调用来满足特殊目的。它可能是C实现的一部分,可能需要与C编译器协调。因此,它并不完全受制于严格遵循C代码的约束,而是可以使用特定于C实现的构造或功能。 (应该清楚地记录这种用法。)通常,用严格符合C的代码编写C实现是不可能的-C实现必须以C标准未指定的方式与硬件和操作系统软件进行交互。即使是在可能的情况下,出于性能和其他目标的考虑,也可能并不理想。

  3. 库的作用之一是完成所有“肮脏”的工作,因此您不必这样做,或者执行艰巨或复杂的工作。旨在供许多人使用的图书馆是投资大量工程工作的好地方,因为少数人的工作可以使许多人受益。考虑到glibc的直接和间接用户数量,投资带来的收益是巨大的,因此,如果能提高性能或可移植性,就值得制作一些奇怪的代码。在某种程度上,需要这种复杂的代码来获得期望的结果,或者比其他方法更需要这种代码,将这些代码放入一个库中可以通过将其放置在一个可供许多人使用的地方来减少世界上此类代码的数量。然后他们不需要编写自己的相似代码即可达到相同的目的。

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