GCC 扩展 —— Constructor(构造函数)
GCC 对 C 语言做了很多扩展,使得 C 语言的表现力得到了很大的增强。
本文主要介绍一下 Constructor 扩展,这个扩展的 C++ 的构造函数很像,它会在 main 函数之前,由程序加载器自动调用,与之相对的是 Destructor(析构函数),它会在 main 函数执行结束或者 exit(退出)的时候自动调用,由于两个扩展是一对,Destructor 这里就不介绍了。ANSI C 标准还引入了 atexit 函数,这个是在进程结束的时候自动调用。和 Destructor 也比较相似。
Constructor 扩展的用法如下:
void func() __attribute__((constructor));
有如下规则:
构造函数先于 main 函数执行。
对齐在同一个文件中的不同的构造函数,则先出现的函数后执行。
对于在不同文件中的构造函数,编译命令中后出现的 .c 文件的构造函数先执行。
利用 Constructor,我们可以定义一些宏来实现模块的自动注册机制。即:我们可以使用宏来自动构造注册函数,然后把注册函数赋予 constructor attribute ,这样我们在添加新的模块的时候就不需要显式的调用注册函数了,只需要在模块文件内加上一个宏调用即可。例子如下:
#define INITIALIZER(f) \
static void f(void) __attribute__((constructor)); \
static void f(void)
#define FAP_REGISTER(name) \
INITIALIZER(fap_register_ ## name) { \
fileaccess_register_entry(&fa_protocol_ ## name); \
}
我们只需要在模块内调用宏 FAP_REGISTER(fs);
即可。这样做的好处是添加新的模块更加方便了,代码维护也更简单。
那么问题来了,假如我们现在有这样一套注册机制的模块,我们把它编译成 .a 库集成到现在的应用方案中,应用没有在任何地方调用我们的注册函数,编译出来的结果能正确自动调用注册函数吗?答案是可能会出问题,定义 constructor attribute 的函数会放在 elf 文件的 constructor 区域。而在链接的时候由于我们的注册函数没被调用,所以编译器很可能做优化,不去链接我们的注册函数,这样 elf 文件的 constructor 区域就没有我们的注册函数,那么自然的,注册函数就不会自动的调用了。
解决办法就是在链接的时候强制把 .a 里所有的函数都链接到可执行文件中。我们可以使用 -wl,--whole-archive
和 -wl,--no-whole-archive
这两个链接命令来做这件事情。
–whole-archive:可以把在其后面出现的静态库包含的函数和变量输出到结果中。
–no-whole-archive:则关掉这个特性。
如果你不想把 –whole-archive 后边所有 .a 库的函数和变量都链接到输出结果的时候就需要用 –no-whole-archive 了。
需要注意的是:--whole-archive
和 --no-whole-archive
是 ld 专有的命令行参数,GCC 并不认识,要通 GCC 传递到 ld,需要在他们前面加 -wl
,例子如下:
gcc main.o -lb.a -wl,--whole-archive -lmode_fs.a -wl,--no-whole-archive -lc.a -lz.a -o main