C语言中的强符号与弱符号

一、概述

在C语言中,函数和初始化的全局变量(包括显示初始化为0)是强符号,未初始化的全局变量是弱符号。

对于它们,下列三条规则使用:

① 同名的强符号只能有一个,否则编译器报"重复定义"错误。

② 允许一个强符号和多个弱符号,但定义会选择强符号的。

③ 当有多个弱符号相同时,链接器选择占用内存空间最大的那个。

二、哪些符号是弱符号?

我们经常在编程中碰到一种情况叫符号重复定义。多个目标文件中含有相同名字全局符号的定义,那么这些目标文件链接的时候将会出现符号重复定义的错误。比如我们在目标文件A和目标文件B都定义了一个全局整形变量global,并将它们都初始化,那么链接器将A和B进行链接时会报错:

1 b.o:(.data+0x0): multiple definition of `global'2 a.o:(.data+0x0): first defined here这种符号的定义可以被称为强符号(Strong Symbol)。有些符号的定义可以被称为弱符号(Weak Symbol)。对于C语言来说,编译器默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号(C++并没有将未初始化的全局符号视为弱符号)。我们也可以通过GCC的"__attribute__((weak))"来定义任何一个强符号为弱符号。注意,强符号和弱符号都是针对定义来说的,不是针对符号的引用。比如我们有下面这段程序:

extern int ext;int weak1;int strong = 1;int __attribute__((weak)) weak2 = 2;int main(){return 0;}上面这段程序中,"weak"和"weak2"是弱符号,"strong"和"main"是强符号,而"ext"既非强符号也非弱符号,因为它是一个外部变量的引用。

下面一段话摘自wikipedia:

In computing, a weak symbol is a symbol definition in an object file or dynamic library that may be overridden by other symbol definitions. Its value will be zero if no definition is found by the loader.

换句话说,就是我们可以定义一个符号,而该符号在链接时可以不解析。

让我们再看一些例子:

$ cat err.cint main(void){f();return 0;}

很明显,不能编译通过,因为’undefined reference’ :

$ gcc err.c/tmp/ccYx7WNg.o: In function `main':err.c:(.text+0x12): undefined reference to `f'collect2: ld returned 1 exit status

那么,如果将符号f声明成弱符号,会怎么呢?

$ cat weak.cvoid __attribute__((weak)) f();int main(void){if (f)f();return 0;}$ gcc weak.c

居然编译通过了,甚至成功执行!让我们看看为什么?

首先声明了一个符号f(),属性为weak,但并不定义它,这样,链接器会将此未定义的weak symbol赋值为0,也就是说f()并没有真正被调用,试试看,去掉if条件,肯定core dump!

我们可以通过nm来查看符号:

$ nm a.out…w f08048324 T main…

如果我们在另一个文件中定义函数f,与week.c一起编译链接,那么函数f就会正确的被调用!

$ cat f.c#include <stdio.h>void f(void){printf("hello from f\n");}$ gcc -c weak.c f.c$ gcc -o weak weak.o f.o$ ./weakhello from f$ nm weak.ow f00000000 T main$ nm f.o00000000 T fU puts$ nm weak…08048384 T f08048354 T mainU puts@@GLIBC_2.0…

我们甚至可以定义强符号来override弱符号:

$ cat orig.c#include <stdio.h>void __attribute__((weak)) f(){printf("original f..\n");}int main(void){f();return 0;}$ gcc orig.c$ ./a.outoriginal f..$ cat ovrd.c#include <stdio.h>void f(void){printf("overridden f!\n");}$ gcc -c orig.c ovrd.c$ gcc -o ovrd orig.o ovrd.o$ ./ovrdoverridden f!$ nm orig.o00000000 W f00000014 T mainU puts$ nm ovrd.o00000000 T fU puts$ nm ovrd…0804838c T f08048368 T mainU puts@@GLIBC_2.0…

或者如下:

$ cat orig-obj.c#include <stdio.h>int __attribute__((weak)) x = 1;int __attribute__((weak)) y = 1;int main(void){printf("x = %d, y = %d\n", x, y);return 0;}$ gcc orig-obj.c$ ./a.outx = 1, y = 1$ cat ovrd-obj.cint x = 2;void f(void){}$ gcc -c orig-obj.c ovrd-obj.c$ gcc -o ovrd-obj orig-obj.o ovrd-obj.o$ ./ovrd-objx = 2, y = 1$ nm orig-obj.o00000000 T mainU printf00000000 V x00000004 V y$ nm ovrd-obj.o00000000 T f00000000 D x$ nm ovrd-obj…08048394 T f08048354 T mainU printf@@GLIBC_2.0080495c8 D x080495c4 V y…

那么当出现multiple symbols时,会如何呢?

$ cat mul.cint main(void){f();return 0;}$ cat s1.c#include <stdio.h>void f(void){printf("1st strong f from %s\n", __FILE__);}$ cat s2.c#include <stdio.h>void f(void){printf("2nd strong f from %s\n", __FILE__);}$ cat w1.c#include <stdio.h>void __attribute__((weak)) f(void){printf("1st weak f from %s\n", __FILE__);}$ cat w2.c#include <stdio.h>void __attribute__((weak)) f(void){printf("2nd weak f from %s\n", __FILE__);}$ gcc -c mul.c s1.c s2.c w1.c w2.c$ gcc -o test1 mul.o s1.o s2.os2.o: In function `f':s2.c:(.text+0x0): multiple definition of `f's1.o:s1.c:(.text+0x0): first defined herecollect2: ld returned 1 exit status$ gcc -o test2 mul.o s1.o w1.o w2.o$ ./test21st strong f from s1.c$ gcc -o test3-1 mul.o w1.o w2.o$ ./test3-11st weak f from w1.c$ gcc -o test3-2 mul.o w2.o w1.o$ ./test3-22nd weak f from w2.c

关于最后一个例子,我想补充的是:如果我们改变给出的编译顺序会怎么样呢?比如像下面这样编译:

$ gcc -o test2 mul.o w1.o s1.o w2.o$ ./test21st strong f from s1.c

看,我将w1.o放在最前面,不过链接器依然选择强符号,这是我们所期望的。

不过,如果我们这样做:

$ ar qs libw.a w1.o w2.o$ ar qs libs.a s1.o s2.o 

再编译:

$ gcc -o test2 mul.o -L. -lw -ls$ ./test2 1st weak f from w1.c

再改成这样编译:

$ gcc -o test2 mul.o -L. -ls -lw$ ./test21st strong f from s1.c

看,情况有变!这是为什么?

原因就是GCC(准确地说是链接器)对待库是不一样的 —— 默认的,链接器使用第一个找到的符号,后面的就不搜索了。

只想到处流浪人生就像一场旅行,不必在乎目的地,

C语言中的强符号与弱符号

相关文章:

你感兴趣的文章:

标签云: