在gcc/clang (C++) 中获取函数作用域外的标签地址
Get a label address out of the function scope in gcc/clang (C++)
由于本地标签地址,我正在制作某种解释器并且正在计算静态常量跳转table。
你知道该怎么做,
static const int JUMP_TABLE[] = { &&case0 - &&case0, &&case1 - &&case0
等等。
由于各种原因,主要是性能,我想 copy/compress 在初始化期间在一个对象中 table。
我的头撞到墙上了,因为我想不出如何逃避函数的词法范围!
我怎样才能以某种方式从另一个函数引用 &&case0?
有人对此有好的技巧吗?
提前致谢
我不知道在纯 GNU C 中实现此目的的方法,因此下面的方法使用其他机制。
双重编译
您可以编译目标文件两次,第一次收集偏移量 运行 并在第二次使用它们。例如
int foo(int x) {
#ifdef GENERATE_ADDRESSES
static __attribute__((section(".foo_offsets"))) unsigned offsets[] = { &&case0 - &&case0, &&case1 - &&case0 };
#endif
switch (x) {
case0:
case 0:
return 1;
case1:
case 1:
return 2;
}
return 0;
}
现在您可以编译,从 .foo_offsets
部分提取字节,并在第二个 运行
上将它们嵌入到您的应用程序中
$ gcc tmp.c -c -DGENERATE_ADDRESSES
$ objcopy -j .foo_offsets -O binary tmp.o
$ xxd -i tmp.o | tee offsets.inc
unsigned char tmp_o[] = {
0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00
};
unsigned int tmp_o_len = 8;
内联汇编
您可以使用内联汇编来全球化标签:
extern char foo_case0[];
extern char foo_case1[];
const void *foo_addresses[] = { &foo_case0[0], &foo_case1[0] };
int foo(int x) {
switch (x) {
case 0:
asm("foo_case0:");
return 1;
case 1:
asm("foo_case1:");
return 2;
}
return 0;
}
不幸的是,在这种情况下,您只能收集 地址 (不是偏移量),因此您需要在启动时手动计算偏移量。
有时转到只是最好的解决方案。非常罕见。多年后它仍然是 c++ 的一部分,这是有充分理由的
所以我所做的是创建一个全局 bool 并在我初始化我的地址数组后设置它。所以第一次调用我的解释器函数时,它加载了一个地址结构,因此所有内容都在同一个函数中。
然后通过对汇编器输出的一些研究,我能够通过如下安排我的代码来节省一些滴答声。
if(is_initialized) .. ex 命令
否则...初始化东西。转到前命令
使用 goto 跳回顶部并执行命令。
我的解释器使用了将近 200 个命令。
使用 switch 语句需要 5-1100 个滴答。取决于命令在列表中的位置
无论命令在列表中的哪个位置,使用 goto 函数[command] 将其减少到 14
这提供了一种纯 C++ 但并非所有编译器都支持的方法
由于本地标签地址,我正在制作某种解释器并且正在计算静态常量跳转table。
你知道该怎么做,
static const int JUMP_TABLE[] = { &&case0 - &&case0, &&case1 - &&case0
等等。
由于各种原因,主要是性能,我想 copy/compress 在初始化期间在一个对象中 table。
我的头撞到墙上了,因为我想不出如何逃避函数的词法范围!
我怎样才能以某种方式从另一个函数引用 &&case0?
有人对此有好的技巧吗?
提前致谢
我不知道在纯 GNU C 中实现此目的的方法,因此下面的方法使用其他机制。
双重编译
您可以编译目标文件两次,第一次收集偏移量 运行 并在第二次使用它们。例如
int foo(int x) {
#ifdef GENERATE_ADDRESSES
static __attribute__((section(".foo_offsets"))) unsigned offsets[] = { &&case0 - &&case0, &&case1 - &&case0 };
#endif
switch (x) {
case0:
case 0:
return 1;
case1:
case 1:
return 2;
}
return 0;
}
现在您可以编译,从 .foo_offsets
部分提取字节,并在第二个 运行
$ gcc tmp.c -c -DGENERATE_ADDRESSES
$ objcopy -j .foo_offsets -O binary tmp.o
$ xxd -i tmp.o | tee offsets.inc
unsigned char tmp_o[] = {
0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00
};
unsigned int tmp_o_len = 8;
内联汇编
您可以使用内联汇编来全球化标签:
extern char foo_case0[];
extern char foo_case1[];
const void *foo_addresses[] = { &foo_case0[0], &foo_case1[0] };
int foo(int x) {
switch (x) {
case 0:
asm("foo_case0:");
return 1;
case 1:
asm("foo_case1:");
return 2;
}
return 0;
}
不幸的是,在这种情况下,您只能收集 地址 (不是偏移量),因此您需要在启动时手动计算偏移量。
有时转到只是最好的解决方案。非常罕见。多年后它仍然是 c++ 的一部分,这是有充分理由的
所以我所做的是创建一个全局 bool 并在我初始化我的地址数组后设置它。所以第一次调用我的解释器函数时,它加载了一个地址结构,因此所有内容都在同一个函数中。
然后通过对汇编器输出的一些研究,我能够通过如下安排我的代码来节省一些滴答声。
if(is_initialized) .. ex 命令 否则...初始化东西。转到前命令
使用 goto 跳回顶部并执行命令。 我的解释器使用了将近 200 个命令。
使用 switch 语句需要 5-1100 个滴答。取决于命令在列表中的位置
无论命令在列表中的哪个位置,使用 goto 函数[command] 将其减少到 14
这提供了一种纯 C++ 但并非所有编译器都支持的方法