由于平时很少用到__attribute__定义函数或者变量的符号属性,所以很难想象C语言可以向C++一样进行函数或者变量的重载。
首先,复习一下有关强符号与弱符号的概念和编译器对强弱符号的处理规则:
1.不同变量与函数所在段
变量类型我们可以分为1)未初始化的,已初始化的;2)全局,局部;3)静态,非静态。
变量类型 | 所在段 |
初始化非静态全局变量 | .data |
初始化静态全局变量 | .data |
初始化静态局部变量 | .data |
初始化绝非静态局部变量 | 运行时动态分配 |
未初始化非静态全局变量 | .bss |
未初始化静态全局变量 | .bss |
未初始化静态局部变量 | .bss |
未初始化非静态局部变量 | 运行时动态分配 |
局部变量无需事先分配到段中,原因是他是用的是进程的栈空间,查看反汇编代码就可以发现,一般函数进入后第一件事情,就是push,把局部变量先放到栈里面,所以局部变量的相对地址在编译后就已经加到指令代码里面了。
假设有如下代码:
int global_init_var = 1;
int global_uninit_var;
static int static_init_var = 2;
static int static_uninit_var; int main(void){
int local_init_var = 3;
int local_uninit_var;
static int static_local_init_var = 4;
static int static_local_uninit_var; global_uninit_var = global_init_var+10;
static_uninit_var = static_init_var+10;
local_uninit_var = local_init_var+10;
static_local_uninit_var = static_local_init_var+10; printf("%d,%d,%d,%d\n", global_uninit_var, static_uninit_var, local_uninit_var, static_local_uninit_var);
return 0;
}
全局未初始化变量其实在汇编完成后被分配为COMMON特殊段内,在链接时候根据程序是否调用此变量决定是否将其置于.bss段中。
2.强符号与弱符号
声明为全局变量和非静态函数,编译器都将其符号视为强符号,而使用__attribute__((weak))可将符号指定为弱符号。
编译器在遇到强、弱符号时,遵循下面的规则进行处理:
1)遇到多个强符号,出错并提示有符号重复定义;
2)遇到一个强符号,多个弱符号,则选用强符号,弱符号被忽略,此时其他文件对其原若符号的操作将产生误操作(弱符号地址指向了强符号地址)
3)多个弱符号,与链接器有关,一般选用占用空间最大的符号。
看一个有趣的代码:
在main.c和foo.c中分别写入foo_only函数
void foo_only(void){
printf("foo_only() is in %s", __FILE__);
}
此时,编译gcc -o test foo.c main.c就会提示符号被重复定义,这是因为foo_only在两个文件中均为强符号。
当我在main.c和foo.c中的任意一个文件内加上void foo_only(void) __attribute__((weak))
那么链接器就会自动链接默认的强符号。
3.C语言重载方法
如果在使用第三方的库或者自己提供库给别人使用时,对库中某些模块不满意,那么应该提供客户一个自给自足的机会。此时,weak属性就非常有用。
实验代码:
foo1.c
foo2.c
运行gcc -o symbol_weak_strong foo1.c foo2.c
1)外部有则引用,没有就用自己的(外部文件函数为强符号,本文件函数为弱符号)
现在我不知道外部文件foo2.c是否有我所使用的函数,那么我先自己在文件foo1.c中定义一个,请注意foo2函数。
在含main的文件将foo2声明为弱符号,而外部文件的函数在无显示声明为弱符号时,均为强符号,因此,
编译器将选择foo2.c中的foo2函数。
2)自己有的就用自己的,没有就引用外部的(外部文件函数声明为弱函数)
如果我对自己的代码水平有足够信心,那么只要我写了,我就用自己的。那么就要求外部文件将函数符号声明为weak,下面的声明语句位于foo.c。
此时,在foo1.c中的函数foo1作为强符号使用,外部文件的foo2.c中的foo1就显式声明为弱符号。
还可以在foo1.c主文件内显式声明外部文件的函数为弱符号:extern void foo1(void) __attribute__((weak)); 这个被称为弱引用。
3)如果不小心两个函数都声明为弱符号,那么编译器怎么选择?
说法一:按照函数占用空间最大的函数作为引用对象;
说法二:按照编译链接顺序进行引用。
由于与编译器有关,所以再次不作详细讨论。
4. C语言的“伪函数重载“与C++的重载区别
C++为了避免C语言那样,不同人开发不同模块代码中,使用了相同的函数或变量名,增加了名称空间和符号修饰来避免多模块之间的符号冲突问题。
(C程序员会尽量避免使用全局变量或者规范命名方法来避免符号冲突,但无法解决这一问题)
GCC对于C++名称修饰方法如下(摘自《程序员的自我修养》):
所有的符号都以”_Z"开头,对于嵌套的名字(在名称空间或在类里面的),后面紧跟“N",然后是各个名称空间和类的名字,每个名字前是名字字符串的长度,再以”E"结尾,如果是函数,则E后面紧跟参数列表的类型简写。比如
namespace N{
class C{
int var;
void func(float);
}
}
作用域为N::C::func的函数经过名称修饰后就是_ZN1N1C4funcEf。
作用域为N::C::var的函数经过名称修饰后就是_ZN1N1C3varE。
使用c++filt可以解析被修饰过的名称。
C++做符号修饰是在汇编阶段完成的;而上述所说的C声明弱符号是作用在链接阶段。