为什么宏在 libc 中可以滥用
Why is macro abusing ok in 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
实施?
风格规则是旨在改善软件工程师(包括团队中的当前工程师、未来将处理代码的工程师以及未来的自己)之间的沟通并减少错误的指南并使软件开发更有效率。这些准则应与其他目标相平衡,例如性能、必要性、解决特定情况下出现的问题等。
与编译器和语言实现相关的库中的软件通常被要求用于特殊目的。它可能是 C 实现的一部分,并且可能需要与 C 编译器协调。因此,它并不完全受制于严格符合 C 代码的约束——它可以使用特定于 C 实现的结构或特性。 (这种使用应该清楚地记录下来。)通常,不可能用严格符合 C 代码的方式编写 C 实现——C 实现必须以 C 标准未指定的方式与硬件和操作系统软件交互。出于性能和其他目标的原因,即使可能,也可能并不理想。
图书馆服务的一个目的是完成所有“肮脏”的工作,因此您不必这样做,或者完成艰苦或复杂的工作。旨在供许多人使用的图书馆是投入大量工程努力的好地方,因为少数人的工作会为许多人带来回报。考虑到 glibc 的直接和间接用户数量,投资回报的杠杆作用是巨大的,因此如果能提高性能或可移植性,值得编写一些奇怪的代码。在某种程度上,需要这种复杂的代码才能获得所需的结果,或者需要替代方法,将这些代码放入库中可以减少世界上此类代码的数量,方法是将它放在一个地方供许多人使用谁不需要编写自己的类似代码来达到相同的目的。
与某些代码相比,问题中显示的代码对预处理器宏的使用相当温和。除了 GCC“标签作为值”扩展(使用 &&
来获取标签的地址)之外,它还使用标准功能并构建一个状态机,任何拥有软件工程学士学位的人都应该熟悉它。在对代码进行一些研究之后,有经验的软件工程师可以识别它在做什么并相当轻松地使用代码。
如果我告诉你我想实现以下代码:
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
实施?
风格规则是旨在改善软件工程师(包括团队中的当前工程师、未来将处理代码的工程师以及未来的自己)之间的沟通并减少错误的指南并使软件开发更有效率。这些准则应与其他目标相平衡,例如性能、必要性、解决特定情况下出现的问题等。
与编译器和语言实现相关的库中的软件通常被要求用于特殊目的。它可能是 C 实现的一部分,并且可能需要与 C 编译器协调。因此,它并不完全受制于严格符合 C 代码的约束——它可以使用特定于 C 实现的结构或特性。 (这种使用应该清楚地记录下来。)通常,不可能用严格符合 C 代码的方式编写 C 实现——C 实现必须以 C 标准未指定的方式与硬件和操作系统软件交互。出于性能和其他目标的原因,即使可能,也可能并不理想。
图书馆服务的一个目的是完成所有“肮脏”的工作,因此您不必这样做,或者完成艰苦或复杂的工作。旨在供许多人使用的图书馆是投入大量工程努力的好地方,因为少数人的工作会为许多人带来回报。考虑到 glibc 的直接和间接用户数量,投资回报的杠杆作用是巨大的,因此如果能提高性能或可移植性,值得编写一些奇怪的代码。在某种程度上,需要这种复杂的代码才能获得所需的结果,或者需要替代方法,将这些代码放入库中可以减少世界上此类代码的数量,方法是将它放在一个地方供许多人使用谁不需要编写自己的类似代码来达到相同的目的。
与某些代码相比,问题中显示的代码对预处理器宏的使用相当温和。除了 GCC“标签作为值”扩展(使用
&&
来获取标签的地址)之外,它还使用标准功能并构建一个状态机,任何拥有软件工程学士学位的人都应该熟悉它。在对代码进行一些研究之后,有经验的软件工程师可以识别它在做什么并相当轻松地使用代码。