一、强符号和弱符号
在C语言中,如果多个模块定义同名全局符号时,链接器认为函数和已初始化的全局变量(包括显示初始化为0)是强符号,未初始化的全局变量是弱符号。
根据这个定义,Linux链接器使用下面的规则来处理多重定义的符号名:
1.不允许有多个同名的强符号
2.如果有一个强符号和多个弱符号同名,那么选择强符号
3.如果有多个弱符号同名,有些编译器从这些弱符号中任意选择一个,有些编译器选择占用内存最大的那个符号
符号链接原理:链接器发现同时存在弱符号和强符号,优先选择强符号,如果发现不存在强符号,只存在弱符号,则选择弱符号
二、weak的使用
对于初始化的各种函数,我们不确定其他地方是否有这个函数,但是我们不得不用这个函数,即在初始化过程中必须得有这个函数。在这种情况下,可以使用__attribute__关键字的weak属性来声明一个弱符号。
//weak.c #include <stdio.h> // int fun1() // { // printf("new %s ",__FUNCTION__); // return 0; // } int main() { fun1(); return 0; }
//test.c #include <stdio.h> int fun1() __attribute__((weak)); int fun1() { printf("%s ",__FUNCTION__); return 0; }
在注释掉weak.c中的fun1时,编译运行
gcc -o weak weak.c teat.c
./weak
输出结果为:
fun1
接下来,我们把对fun1的注释去掉,同样编译运行,运行结果为:
new fun1
三、alias的使用
在使用weak声明弱符号之后,我们需要将每个弱符号函数都定义为一个空函数,如果每个函数都分开写的话,重复量很大,也比较浪费时间。所以可以用alias属性来给每个函数起别名。
#include <stdio.h> int fun1() { printf("%s ",__FUNCTION__); return 0; } int fun() __attribute__((alias("fun1"))); int main() { fun(); return 0; }
代码中fun的别名是fun1,所以运行结果为:
fun1
四、weak和alias结合使用
//test_attribute.c #include <stdio.h> int fun1() { printf("%s ",__FUNCTION__); return 0; } int fun2() __attribute__((weak, alias("fun1"))); int fun3() __attribute__((weak, alias("fun1")));
//attribute.c #include <stdio.h> int fun2() { printf("%s ",__FUNCTION__); return 0; } int main() { fun2(); fun3(); return 0; }
fun2是强符号,fun3没有初始化,而fun2和fun3的别名均为fun1,运行结果为:
fun2
fun1
五、使用weak和alias对可执行文件的影响
先看使用weak和alias时的代码
//test_attribute.c #include <stdio.h> int fun1() { printf("%s ",__FUNCTION__); return 0; } int fun2() __attribute__((weak, alias("fun1"))); int fun3() __attribute__((weak, alias("fun1"))); int fun4() __attribute__((weak, alias("fun1"))); int fun5() __attribute__((weak, alias("fun1"))); int fun6() __attribute__((weak, alias("fun1"))); int fun7() __attribute__((weak, alias("fun1"))); int fun8() __attribute__((weak, alias("fun1"))); int fun9() __attribute__((weak, alias("fun1"))); int fun10() __attribute__((weak, alias("fun1"))); int fun11() __attribute__((weak, alias("fun1"))); int fun12() __attribute__((weak, alias("fun1"))); int fun13() __attribute__((weak, alias("fun1")));
//attribute.c #include <stdio.h> int main() { fun2(); fun3(); fun4(); fun5(); fun6(); fun7(); fun8(); fun9(); fun10(); fun11(); fun12(); fun13(); return 0; }
链接之后生成的文件大小为:8987。那么如果将weak和alias去掉,同时将这么多fun全部重新定义,
//test_attribute.c #include <stdio.h> int fun1() { printf("%s ",__FUNCTION__); return 0; } // int fun2() __attribute__((weak, alias("fun1"))); // int fun3() __attribute__((weak, alias("fun1"))); // int fun4() __attribute__((weak, alias("fun1"))); // int fun5() __attribute__((weak, alias("fun1"))); // int fun6() __attribute__((weak, alias("fun1"))); // int fun7() __attribute__((weak, alias("fun1"))); // int fun8() __attribute__((weak, alias("fun1"))); // int fun9() __attribute__((weak, alias("fun1"))); // int fun10() __attribute__((weak, alias("fun1"))); // int fun11() __attribute__((weak, alias("fun1"))); // int fun12() __attribute__((weak, alias("fun1"))); // int fun13() __attribute__((weak, alias("fun1")));
//attribute.c #include <stdio.h> int fun2() { printf("%s ",__FUNCTION__); return 0; } int fun3() { printf("%s ",__FUNCTION__); return 0; } int fun4() { printf("%s ",__FUNCTION__); return 0; } int fun5() { printf("%s ",__FUNCTION__); return 0; } int fun6() { printf("%s ",__FUNCTION__); return 0; } int fun7() { printf("%s ",__FUNCTION__); return 0; } int fun8() { printf("%s ",__FUNCTION__); return 0; } int fun9() { printf("%s ",__FUNCTION__); return 0; } int fun10() { printf("%s ",__FUNCTION__); return 0; } int fun11() { printf("%s ",__FUNCTION__); return 0; } int fun12() { printf("%s ",__FUNCTION__); return 0; } int fun13() { printf("%s ",__FUNCTION__); return 0; } int main() { fun2(); fun3(); fun4(); fun5(); fun6(); fun7(); fun8(); fun9(); fun10(); fun11(); fun12(); fun13(); return 0; }
在这种情况下,生成的可执行文件的大小为:9473。可见利用weak和alias可以节省可执行文件的占用空间。
六、函数有形参的情况
如果函数有形参,具体情况是什么呢?看下面代码,如果别名函数有形参,
//test_attribute.c #include <stdio.h> int fun1(int a, int b) { a = 3; printf("%s, %d ", __FUNCTION__, a); return 0; } int fun2() __attribute__((weak, alias("fun1")));
//attribute.c #include <stdio.h> int main() { fun2(); return 0; }
运行结果为:
fun1, 3
另外,还有一种情况,强符号函数声明有形参
//test_attribute.c #include <stdio.h> int fun1(int a, int b) { a = 3; printf("%s, %d ", __FUNCTION__, a); return 0; } int fun2() __attribute__((weak, alias("fun1"))); int fun3() __attribute__((weak, alias("fun1"))); int fun4() __attribute__((weak, alias("fun1"))); int fun5() __attribute__((weak, alias("fun1"))); int fun6() __attribute__((weak, alias("fun1"))); int fun7() __attribute__((weak, alias("fun1"))); int fun8() __attribute__((weak, alias("fun1"))); int fun9() __attribute__((weak, alias("fun1"))); int fun10() __attribute__((weak, alias("fun1"))); int fun11() __attribute__((weak, alias("fun1"))); int fun12() __attribute__((weak, alias("fun1"))); int fun13() __attribute__((weak, alias("fun1")));
//attribute.c #include <stdio.h> int fun2(int a, int b) { a = 5; printf("%s, %d ", __FUNCTION__, a); return 0; } int main() { int a; int b; fun2(a, b); return 0; }
运行结果为:
fun2, 5
个人理解:
在利用weak和alias设置弱符号函数的时候,实际是将fun2指向了fun1,
而如果另外定义了同样的fun2后,它属于强符号,那么test_sttribute.c声明中的fun2指向的是这个新定义的fun2,
这种实现是对函数名地址的改变,而函数内的形参在设置weak和alias可以不用写入