C 预处理器中指向 void 的指针
Pointer to void in C preprocessor
我阅读了这个来源 (https://github.com/lattera/glibc/blob/master/stdio-common/vfprintf.c) 并找到了一些我不完全理解的有趣的行:
#ifdef SHARED
/* 'int' is enough and it saves some space on 64 bit systems. */
# define JUMP_TABLE_TYPE const int
# define JUMP_TABLE_BASE_LABEL do_form_unknown
# define REF(Name) &&do_##Name - &&JUMP_TABLE_BASE_LABEL
# define JUMP(ChExpr, table) \
do \
{ \
int offset; \
void *ptr; \
spec = (ChExpr); \
offset = NOT_IN_JUMP_RANGE (spec) ? REF (form_unknown) \
: table[CHAR_CLASS (spec)]; \
ptr = &&JUMP_TABLE_BASE_LABEL + offset; \
goto *ptr; \
} \
while (0)
...
#define STEP0_3_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_hash), /* for '<hash>' */ \
REF (flag_zero), /* for '0' */ \
REF (flag_quote), /* for '\'' */ \
REF (width_asterics), /* for '*' */ \
REF (width), /* for '1'...'9' */ \
REF (precision), /* for '.' */ \
REF (mod_half), /* for 'h' */ \
...
我写了一个简单的例子,明白这一行 &&do_##Name
将 do_##Name
转换为指向 void 的指针。但是我不明白在这种情况下指针算法是如何工作的:#define REF(Name) &&do_##Name - &&JUMP_TABLE_BASE_LABEL
有人可以写简单的解释吗?或者写一些 link 到 Internet 资源,在那里我可以阅读有关此技术的信息。
大概是为了保证具有线性复杂性,代码使用了由用作值的标签组成的跳转table。
Labels-as-values 是一个 GNU C 扩展,它允许您使用 &&
获取标签的地址。输入地址 void *
,然后您可以使用 goto *address;
跳转到它。
基本标签的小变化是,代码不是在 table 中存储绝对标签,而是存储 do_uknown_form
标签的偏移量。
这在 table 中节省了 space(偏移量可以是 4 字节整数而不是 8 字节指针)并有助于为共享库生成更好的代码(因此 #ifdef SHARED
) 因为当代码加载到 relocatable 共享库中时,即使是绝对标签的 static const
跳转 table 也需要修补,但偏移量保持不变,因此需要修补消失,table 可以存储在 read-only 内存中。
该技术在 Ulrich Drepper 的 How to Write Shared Libraries 文章中有所描述。
我阅读了这个来源 (https://github.com/lattera/glibc/blob/master/stdio-common/vfprintf.c) 并找到了一些我不完全理解的有趣的行:
#ifdef SHARED
/* 'int' is enough and it saves some space on 64 bit systems. */
# define JUMP_TABLE_TYPE const int
# define JUMP_TABLE_BASE_LABEL do_form_unknown
# define REF(Name) &&do_##Name - &&JUMP_TABLE_BASE_LABEL
# define JUMP(ChExpr, table) \
do \
{ \
int offset; \
void *ptr; \
spec = (ChExpr); \
offset = NOT_IN_JUMP_RANGE (spec) ? REF (form_unknown) \
: table[CHAR_CLASS (spec)]; \
ptr = &&JUMP_TABLE_BASE_LABEL + offset; \
goto *ptr; \
} \
while (0)
...
#define STEP0_3_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_hash), /* for '<hash>' */ \
REF (flag_zero), /* for '0' */ \
REF (flag_quote), /* for '\'' */ \
REF (width_asterics), /* for '*' */ \
REF (width), /* for '1'...'9' */ \
REF (precision), /* for '.' */ \
REF (mod_half), /* for 'h' */ \
...
我写了一个简单的例子,明白这一行 &&do_##Name
将 do_##Name
转换为指向 void 的指针。但是我不明白在这种情况下指针算法是如何工作的:#define REF(Name) &&do_##Name - &&JUMP_TABLE_BASE_LABEL
有人可以写简单的解释吗?或者写一些 link 到 Internet 资源,在那里我可以阅读有关此技术的信息。
大概是为了保证具有线性复杂性,代码使用了由用作值的标签组成的跳转table。
Labels-as-values 是一个 GNU C 扩展,它允许您使用 &&
获取标签的地址。输入地址 void *
,然后您可以使用 goto *address;
跳转到它。
基本标签的小变化是,代码不是在 table 中存储绝对标签,而是存储 do_uknown_form
标签的偏移量。
这在 table 中节省了 space(偏移量可以是 4 字节整数而不是 8 字节指针)并有助于为共享库生成更好的代码(因此 #ifdef SHARED
) 因为当代码加载到 relocatable 共享库中时,即使是绝对标签的 static const
跳转 table 也需要修补,但偏移量保持不变,因此需要修补消失,table 可以存储在 read-only 内存中。
该技术在 Ulrich Drepper 的 How to Write Shared Libraries 文章中有所描述。