使用GNU C扩展实现C的反射

说明:此机制并非传统意义上的反射,仅为一种类似的实现

我最近在写一个 Sandbox,需要根据命令行参数选择对应的规则函数执行。如果硬编码,这是非常痛苦的,所以我在 Google 搜了一会,发现了如下的办法,相比硬编码要好许多。

什么是 GNU C 扩展

众所周知,编译器不只是在翻译,还有优化、错误检查等许多工作,所以就催生了许多编译属性标识的产生。

你翻阅许多开源项目源码的时候肯定会看到一堆 define 和下划线的东西,比如__attribute____builtin_expect等。这些标识都是在给编译器传递信息,告诉它需要做什么事情,从而使得编译工作可以根据需要灵活调整。

这些被称为 C Extensions,即 GNU C 扩展

__attribute__((section))

在如此之多的扩展之中,有一项__attribute__((section)),用于修饰变量

根据官网文档的说明:

Normally, the compiler places the objects it generates in sections like data and bss. Sometimes, however, you need additional sections, or you need certain particular variables to appear in special sections, for example to map to special hardware. The section attribute specifies that a variable (or function) lives in a particular section.

通常来说,编译器把生成的对象放在bssdata这样的段。但是有时候你需要把对象放到一个特定的段中,比如特定的硬件映射。

这个标识符将被修饰对象放置于section段中

所以,你可以做到这样的事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 定义变量,并放置于test段
int __attribute__((section("mysec"))) a = 123;
int __attribute__((section("mysec"))) b = 456;

// 你尽管用extern声明,编译器会自动处理
extern int __start_mysec;
extern int __stop_mysec;

void func()
{
int* mysec = &__start_mysec;
int my_a = mysec[0];
int my_b = mysec[1];
printf("a=%d b=%d\n", my_a, my_b); // a=123 b=456
}

编译器会自动定义__start##section__end##section两个变量,分别指向该段的头尾,你可以使用它们进行访问范围控制

另外还需要注意的:

  • Use the section attribute with global variables and not local variables

    只能修饰全局变量,所以注意作用域!

实现反射机制

直接把成品摆出来,下面解释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
#include <string.h>
struct func
{
void (*fn)(void);
const char *func_name;
};

#define reflect(x) __attribute__((section("functions"))) struct func __##x = {x, #x};

void func1(void)
{
printf("func1\n");
}

void func2(void)
{
printf("func2\n");
}
reflect(func1);
reflect(func2);

int main(void)
{
char * function_to_be_executed = "func2";
extern struct func __start_functions;
extern struct func __stop_functions;
for(struct func *f = &__start_functions; f < &__stop_functions; f++)
{
if(strcmp(f->func_name, function_to_be_executed) == 0)
{
f->fn();
}
}
return 0;
}
1
2
3
bakaft@BakaFT-PC:~/oj$ gcc section_test.c 
bakaft@BakaFT-PC:~/oj$ ./a.out
func2

可以看到,这里根据我们定义的字符串,执行了对应的函数,也就达到了目的。

原理

1
#define reflect(x) __attribute__((section("functions"))) struct func __##x = {x, #x};

根据刚才的知识,这个宏的结果就是把一个struct func放到了程序的·functions

这个结构体存放了一个指向函数x的函数指针,和一个值为x的字符串。这样就可以在后面使用循环,遍历functions段,寻找到我们需要的函数了

参考

利用__attribute__((section))实现 C 语言反射 | 自增人生 (ixx.life)

Author

BakaFT

Posted on

2022-09-30

Updated on

2023-12-28

Licensed under

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×