在具有 gnu 链接器和编译器或兼容的系统上,可以将某些对象放在不同的部分中。然后,链接器将为节的开头和结尾生成符号。使用它,您可以将所有结构放入不同对象的该部分中,链接器将在链接时合并这些部分,您可以将它们作为数组访问。如果您在共享库中执行此操作,并且在 ELF Linux/*BSD 之外绝对不可移植,则这需要更多的操作。
我在 MacOS 和 a.out BSD 上做过类似的事情(虽然这个例子不起作用),但是我丢失了那个代码。这是一个如何在 Linux 上运行的示例:
$ cat link_set.c
#include <stdio.h>
struct dispatch_table {
const char *name;
void (*fun)(int);
};
#define ADD_TO_DISPATCH_TABLE(name, fun) \
static const struct dispatch_table entry_##fun \
__attribute__((__section__("link_set_dispatch_table"))) = \
{ name, fun }
int
main(int argc, char **argv)
{
extern struct dispatch_table __start_link_set_dispatch_table;
extern struct dispatch_table __stop_link_set_dispatch_table;
struct dispatch_table *dt;
for (dt = &__start_link_set_dispatch_table; dt != &__stop_link_set_dispatch_table; dt++) {
printf("name: %s\n", dt->name);
(*dt->fun)(0);
}
return 0;
}
void
fun1(int x)
{
printf("fun1 called\n");
}
ADD_TO_DISPATCH_TABLE("fun 1", fun1);
void
fun2(int x)
{
printf("fun2 called\n");
}
ADD_TO_DISPATCH_TABLE("fun 2", fun2);
$ cc -o link_set link_set.c
$ ./link_set
name: fun 1
fun1 called
name: fun 2
fun2 called
$
解释宏的作用。它创建了一个结构 dispatch_table ,其名称我们希望是唯一的,因为您可能希望在一个对象中多次使用它(如果您多次使用相同的函数,请找出其他方法来命名该结构)并使用gnu 扩展来指定对象应该以哪个部分结束。如果我们将对象放入“some_section_name”,那么链接器将自动添加符号 __start_some_section_name 和 __end_some_section_name。然后我们可以在这些符号之间走动,并获得我们放入该部分的所有结构。
更新了一个适用于 MacOS 的工作示例:
#include <stdio.h>
#include <mach-o/ldsyms.h>
#include <mach-o/getsect.h>
#include <mach-o/loader.h>
struct dispatch_table {
const char *name;
void (*fun)(int);
};
#define ADD_TO_DISPATCH_TABLE(name, fun) \
static const struct dispatch_table entry_##fun \
__attribute__((__section__("__DATA,set_dt"))) = \
{ name, fun }
int
main(int argc, char **argv)
{
struct dispatch_table *start;
unsigned long sz;
intptr_t s;
int i;
s = (intptr_t)getsectdata("__DATA", "set_dt", &sz);
if (s == 0)
return 1;
s += _dyld_get_image_vmaddr_slide(0);
start = (struct dispatch_table *)s;
sz /= sizeof(*start);
for (i = 0; i < sz; i++) {
struct dispatch_table *dt = &start[i];
printf("name: %s\n", dt->name);
(*dt->fun)(0);
}
return 0;
}
void
fun1(int x)
{
printf("fun1 called\n");
}
ADD_TO_DISPATCH_TABLE("fun 1", fun1);
void
fun2(int x)
{
printf("fun2 called\n");
}
ADD_TO_DISPATCH_TABLE("fun 2", fun2);
该部分必须在此处称为“set_dt”,因为在此可执行格式中,部分名称的长度受到限制。
当然,如果你已经到了需要这个的地步,你肯定明白这一切都是非常危险的,不可移植的,不能保证永远工作(我三年前的代码在当前版本的 macos 上不起作用) , 没有类型或其他安全性,如果其他东西决定把东西放在同名的部分,事情最终会变成非常漂亮的烟花。但这是一个非常巧妙的技巧。我在两个大型项目中使用了这种方法,它确实节省了大量工作,中央调度表不必在曾经给每个人带来冲突的共享存储库中编辑。