最近查看linux内核代码时,表现了一些编译器选项如__attribute_((weak))、__attribute__( (alias("target"))),一开始不了解,后来自己查看资料及书籍算是对gcc的这个编译属性有了一定的认识。
一、先了解weak属性。
__attribute__((weak))表示为弱符号属性,所谓的弱符号是针对于强符号来说的,我们定义的全局已初始化变量及全局函数等都是属于强符号,在链接时如果有多个强符号就会报错误;而弱符号主要指未初始化的全局变量或通过__attribute__((weak))来显式申明的变量或函数。
以下代码示例:
/* file:weak_test.c */
void weak_func_test(void) __attribute__((weak)); /* 显式申明为weak,属于弱符号(函数) */
int weak_var_test; /* 未初始化的全局变量,属于弱符号 */
#ifdef WEAK_SYM
void weak_func_test(void)
{
printf("%s:%s.c in\n", __FILE__, __func__);
}
#endif
int main()
{
printf("weak_var_test:%d\n", weak_var_test);
weak_func_test();
return 0;
}
/* file:symbol.c */
int weak_var_test = 6666; /* 已初始化的全局变量,属于强符号 */
/* 全局函数属于强符号 */
void weak_func_test(void)
{
printf("%s:%s() in\n", __FILE__, __func__);
}
1、使用编译命令gcc weak_test.c symbol.c DWEAK_SYM -o weak_test,执行./weak_test,打印weak_var_test为6666,函数weak_func_test()打印symblo.c:weak_func_test() in(注意是symbol.c的函数),从执行结果看symbol.c的weak_var_test及weak_func_test覆盖了weak_test.c的符号,说明链接时强弱符号都存在时以强符号为准;
2、再使用编译命令而gcc weak_test.c -DWEAK_SYM -o weak_test,执行./weak_test,打印weak_var_test为0(未初始化的全局变量编译器默认为0),函数执行打印weak_test.c:weak_func_test() in(这时是weak_test.c的函数),说明连接时如果只有弱符号时以弱符号为准。
3、继续编译gcc weak_test.c -o weak_test,这时可能大家会有疑问,weak_func_test函数没有实现,那么链接的时候应该会报错吧;实际上肯定是不会的,就是因为我们 将这个函数显式的申明为weak symbol,申明为weak symbol的函数在.o目标文件里面是以WEAK及UND形式存在的,符号的地址为0,具体可以用readelf -s 命令查看。
那么这种情况下只有弱符号weak_func_test存在,最终链接时也以弱符号为准,只不过此函数的地址为0,所以这时我们去执行./weak_test的时候必然会有segement fault的错误产生,就是因为去访问了null指针。
4、弱符号还有一个规则,就是两个都是弱符号时,以内存占用大小较大的那个符号为准。比如未初始化的char var和long var同时存在时,链接器以实际sizeof(long)的大小来给var分配空间,实例就不讲述了,遇见这种情况需要额外小心。
小结:weak属性基本已讲述完成,其实弱符号在实际中也有很多应用。比如说在一个库里面实现某个函数,申明为弱符号,在某种情况下我们可以用自己的代码去覆盖库的实现从而重新去实现某个函数,达到定制化的目的。
二、接下去讲述alias属性,alias属性比较简单,从字面意思理解就是给符号设置一个别名,相当于取一个外号。使用方法如下:
void func(void);
void alias_func(void) __attribute__((alias("func"))); 需要注意c++的符号修饰机制!
这样的意思就是函数func的别名或外号是alias_func,那么就是调用alias_func()和func()的效果是一样的,有兴趣的话可以自己写代码验证。这时需要主意func函数必须是要有定义的,否则会编译报错的。
三、最后还有一个属性是weakref活weakref("target")
__attribute__((weakref))为弱引用,请注意引用与定义的区别。weakref就是申明某个引用为弱引用,弱引用时如果需引用符号不存在也不会链接出错,而是将需要引用的符号定义为WEAK属性及0地址(跟前面的WEAK属性很相似吧)。
weakref的用法有点特别,必须要配合alias使用及必须是static定义。__attribute__((weak("target")))相当于__attribute__((weakref,alias("target"))),以下看个实例:
/*
** weakref_test.c
*/
/* 申明func_alias函数func的带弱引用的别名 */
void func(void)
{
printf("func:%s in\n", __FUNC__);
}
static void func_alias(void) __attribute__((weakref,alias("func")));
int main()
{
func_alias(); /* 相当于调用func */
return 0;
}
编译运行,会发现实际运行的就是func函数。func_alias相当于是func的一个带有weakref属性的另一份申明,可以这样理解:void *func = func;void *func_alias = func("weakref")。
注意到前面alias属性如果func不存在时申明alias会出错,通过weakref方法,可以让func未定义就可以编译通过,使用static void alias_func(void) __attribute__((weakref, alias("func")))时即使func未定义也能链接通过,只不过func或alias_func的地址为0,可以去掉func的实现,验证一下 即可。
上面讲述的关于weak、alias、weakref属性都是自己的一些总结,有很多不合理之处,还望大家指出,一起探讨。
gcc版本信息:gcc version 4.4.7
gcc官方文档,权威解释:
http://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes