天天看點

C語言中的強符号與弱符号一、概述二、哪些符号是弱符号?三、如何避免呢?補充:

轉自C語言中的強符号與弱符号

一、概述

在C語言中,函數和初始化的全局變量(包括初始化為0)是強符号,未初始化的全局變量是弱符号。

對于它們,下列三條規則使用:

① 同名的強符号隻能有一個,否則編譯器報"重複定義"錯誤。

② 允許一個強符号和多個弱符号,但定義會選擇強符号的。

③ 當有多個弱符号相同時,連結器選擇占用記憶體空間最大的那個。

二、哪些符号是弱符号?

我們經常在程式設計中碰到一種情況叫符号重複定義。多個目标檔案中含有相同名字全局符号的定義,那麼這些目标檔案連結的時候将會出現符号重複定義的錯誤。比如我們在目标檔案A和目标檔案B都定義了一個全局整形變量global,并将它們都初始化,那麼連結器将A和B進行連結時會報錯:

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 weak;
int strong = 1;
__attribute__((weak)) weak2 = 2;
int main()
{
        return 0;
}
           

上面這段程式中,"weak"和"weak2"是弱符号,"strong"和"main"是強符号,而"ext"既非強符号也非弱符号,因為它是一個外部變量的引用。   下面一段話摘自 wikipedia:

In computing, aweak symbol is a symbol definition in anobject file ordynamic library that may be overridden by other symbol definitions. Its value will be zero if no definition is found by the loader.

換句話說,就是我們可以定義一個符号,而該符号在連結時可以不解析。

讓我們再看一些例子:

$ cat err.c
int 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.c
void __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 f
08048324 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
$ ./weak
hello from f

$ nm weak.o
w f
00000000 T main
$ nm f.o
00000000 T f
U puts
$ nm weak
...
08048384 T f
08048354 T main
U [email protected]@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.out
original 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
$ ./ovrd
overridden f!
           
$ nm orig.o
00000000 W f
00000014 T main
U puts
$ nm ovrd.o
00000000 T f
U puts
$ nm ovrd
...
0804838c T f
08048368 T main
U [email protected]@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.out
x = 1, y = 1
           
$ cat ovrd-obj.c
int x = 2;
void f(void)
{
}
$ gcc -c orig-obj.c ovrd-obj.c
$ gcc -o ovrd-obj orig-obj.o ovrd-obj.o
$ ./ovrd-obj
x = 2, y = 1
           
$ nm orig-obj.o
00000000 T main
U printf
00000000 V x
00000004 V y
$ nm ovrd-obj.o
00000000 T f
00000000 D x
$ nm ovrd-obj
...
08048394 T f
08048354 T main
U [email protected]@GLIBC_2.0
080495c8 D x
080495c4 V y
...
           

那麼當出現multiple symbols時,會如何呢?

$ cat mul.c
int 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.o
s2.o: In function `f':
s2.c:(.text+0x0): multiple definition of `f'
s1.o:s1.c:(.text+0x0): first defined here
collect2: ld returned 1 exit status


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


$ gcc -o test3-1 mul.o w1.o w2.o
$ ./test3-1
1st weak f from w1.c
$ gcc -o test3-2 mul.o w2.o w1.o
$ ./test3-2
2nd weak f from w2.c
           

關于最後一個例子,我想補充的是:如果我們改變給出的編譯順序會怎麼樣呢?比如像下面這樣編譯:

$ gcc -o test2 mul.o w1.o s1.o w2.o
$ ./test2
1st 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
$ ./test2
1st strong f from s1.c
           

看,情況有變!這是為什麼?

原因就是GCC(準确地說是連結器)對待庫是不一樣的 —— 預設的,連結器使用第一個找到的符号,後面的就不搜尋了。

不過我們也可以強制連結器搜尋所有的庫,辦法如下:

$ ar qs libw.a w1.o w2.o
$ ar qs libs.a s1.o s2.o
$ gcc -o test2 mul.o -L. -Wl,--whole-archive -lw -ls -Wl,--no-whole-archive
./libs.a(s2.o): In function `f':
s2.c:(.text+0x0): multiple definition of `f'
./libs.a(s1.o):s1.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
           

重新如下操作:

$ rm libw.a libs.a
$ ar qs libw.a w1.o w2.o
$ ar qs libs.a s1.o
$ gcc -o test2 mul.o -L. -Wl,--whole-archive -lw -ls -Wl,--no-whole-archive
$ ./test2
1st strong f from s1.c
           

現在可以了,盡管-lw在前!

讓我們再來看一個具體的例子:

// main.c
#include <stdio.h>
#include <stdlib.h>

extern int fun(void);

int global_var1 = 0xff00ff00;		// 強符号
int global_var2 = 0x00ff00ff;		// 強符号

int main(int argc, const char *argv[])
{
/
	printf("in main.c: &global_var1 = %p", &global_var1);
	printf(" global_var1 = %x\n", global_var1);
	printf("sizeof(global_var1) = %d\n", sizeof(global_var1));
/
	printf("in main.c: &global_var2 = %p", &global_var2);
	printf(" global_var2 = %x\n", global_var2);
	printf("sizeof(global_var2) = %d\n", sizeof(global_var2));
/
	fun();

	printf("global_var1 = %x\n", global_var1);
	printf("global_var2 = %x\n", global_var2);

	return 0;
}
           
// test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

double global_var1;

int fun(void)
{
	printf("in test.c: &global_var1 = %p", &global_var1);
	printf(" global_var1 = %x\n", global_var1);
	printf("sizeof(global_var1) = %d\n", sizeof(global_var1));

	memset(&global_var1, 0, sizeof(global_var1));

	return 0;
}
           

在gcc中編譯得到如下結果:

C語言中的強符号與弱符号一、概述二、哪些符号是弱符号?三、如何避免呢?補充:

我們可以看到,在main.c和test.c都有一個global_var1,在main.c中的為強符号,在test.c中的為弱符号。因為在test.c中global_var1沒有初始化,是以根據規則②得知:編譯器選擇main.c中的global_var1的值初始化那片記憶體。不要誤認為在test.c中使用global_var1時是用的main.c中的global_var1,我之前錯誤得這樣認為。其實是這樣的:main.c中的global_var1和test.c中的global_var1引用的時同一塊記憶體區域,隻是在兩個檔案中代表的意義不同  ---- 在main.c中代表一個int型變量,在test.c中代表一個double型變量,它們的起始位址相同,但占用記憶體空間是不同的, 在main.c中占用4個位元組,在test.c中占用8個位元組,這點從上圖的兩個sizeof輸出結果中可以得到驗證。

(gdb) break main
Breakpoint 1 at 0x804841d: file main.c, line 14.
(gdb) run
Starting program: /home/astrol/c/elf/dynamic/understand_weak_symbol_by_example/main

Breakpoint 1, main (argc=1, argv=0xbffff6d4) at main.c:14
14              printf("in main.c: &global_var1 = %p", &global_var1);
(gdb) print/x &global_var1
$1 = 0x804a018
(gdb) print/x &global_var2
$2 = 0x804a01c
(gdb) x/8xb &global_var1
0x804a018 <global_var1>:        0x00    0xff    0x00    0xff    0xff    0x00    0xff    0x00
(gdb)
           

因為在test.c中的global_var1占用八個位元組,memset(&global_var1, 0, sizeof(global_var1))将這塊記憶體區域清零,這也就解釋了為什麼調用fun之後,global_var1和global_var2都變成0的緣故。

(gdb) break 27
Breakpoint 1 at 0x80484d2: file main.c, line 27.
(gdb) run
Starting program: /home/astrol/c/elf/dynamic/understand_weak_symbol_by_example/main
in main.c: &global_var1 = 0x804a018 global_var1 = ff00ff00
sizeof(global_var1) = 4
in main.c: &global_var2 = 0x804a01c global_var2 = ff00ff
sizeof(global_var2) = 4
in test.c: &global_var1 = 0x804a018 global_var1 = ff00ff00
sizeof(global_var1) = 8
global_var1 = 0
global_var2 = 0

Breakpoint 1, main (argc=1, argv=0xbffff6d4) at main.c:27
27              return 0;
(gdb) print/x &global_var1
$1 = 0x804a018
(gdb) print/x &global_var2
$2 = 0x804a01c
(gdb) x/8xb &global_var1
0x804a018 <global_var1>:        0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
(gdb)
           

可見在test.c中對global_var1的改動會影響main.c中global_var1和global_var2的值。當程式很大時,這種錯誤很難發現,是以盡量避免不同類型的符号在多個檔案中。

三、如何避免呢?

1、上策:想辦法消除全局變量。全局變量會增加程式的耦合性,對他要控制使用。如果能用其他的方法代替最好。

2、中策:實在沒有辦法,那就把全局變量定義為static,它是沒有強弱之分的。而且不會和其他的全局符号産生沖突。至于其他檔案可能對他的通路,可以封裝成函數。把一個子產品的資料封裝起來是一個好的實踐。

3、下策:把所有的符号全部都變成強符号。所有的全局變量都初始化,記住,是所有的,哪怕初始值是0都行。如果一個沒有初始化,就可能會和其他人産生沖突,盡管别人初始化了。(自己寫代碼測試一下)。

4、必備之策:GCC提供了一個選項,可以檢查這類錯誤:-fno-common。

參考連結: http://blog.csdn.net/chgaowei/article/details/7173436

http://www.embedded-bits.co.uk/2008/gcc-weak-symbols/ (GCC Weak Symbols)

http://write.blog.csdn.net/postedit/8008629 ( 什麼是weak symbol?)

https://www.jlf.mobi/browse.php?u=1hI1aPuMOmMwQTIqb6h%2FqO8F5%2FYVOOkhlX2bMwie7AJ50MN8EXhHmZmz8fHZ%2FktaE4fyb2wM5LXUobHOtL3LZUgb6l%2FBwQYOqsPY&b=6 (Understand Weak Symbols by Examples)

https://www.jlf.mobi/browse.php?u=sNf8ST%2FVP9gafgKA%2Bruq%2BtTek4VmvLmF%2B2gRQiHSA1hAjmyGbfEQlvH%2FucHd69VUYWlTtAnatKpxos64H2mn09iIm4q3awxURLLEMPVl&b=6 (Symbol Resolution, Weak Symbols, How compiler resolves multiple Global Symbols)

http://wanderingcoder.net/2012/06/30/multiply-defined-symbols/  ( Dealing with multiply defined symbols)

補充:

最近在看《程式員的自我修養》,知道現在的編譯器和連結器支援一種叫COMMOM塊(Common Block)的機制,這種機制用來解決 一個弱符号定義在多個目标檔案中,而它們的類型又不同 的情況。

在目标檔案中,編譯器将未初始化的全局變量放在了COMMON段,未初始化的靜态變量放在BSS段。

----------------------------------------------------------------------------------------------------------------

對于全局變量來說,如果初始化了不為0的值,那麼該全局變量存儲在.data段;

如果初始化的值為0, 那麼将其存儲在.BSS;(依然是強符号)

如果沒有初始化,那麼将其存儲在COMMON塊,等到連結時再将其放入到.BSS段。(這點不同的編譯器會有所不同,有的編譯器會把沒有初始化的全局變量放在.BSS段)

----------------------------------------------------------------------------------------------------------------

為什麼這樣處理呢?

我們可以想到,當編譯器将一個編譯單元編譯成目标檔案的時候,如果該編譯單元包含了弱符号(未初始化的全局變量就是典型的弱符号),那麼該弱符号最終所占空間的大小此時是未知的,因為有可能其他編譯單元中該符号所占的空間比本編譯單元該符号所占的空間要大。是以編譯器此時無法為該弱符号在BSS段配置設定空間,因為所需要得空間的大小未知。但是連結器在連結過程中可以确定弱符号的大小,因為當連結器讀取所有輸入目标檔案後,任何一個弱符号的最終大小都可以确定了,是以它可以在最終的輸出檔案的BSS段為其配置設定空間。是以總體來看,未初始化的全局變量還是被放在BSS段。       ------摘自《程式員的自我修養》

來看一個例子:

/* aa.c  */
#include <stdio.h>
int global ;    /* weak symbol */
int main(int argc, const char *argv[])
{
        printf("global = %d, sizeof(global) in main = %d\n", global, sizeof(global));
        bb();
        return 0;
}<span style="background-color: inherit; font-family: Consolas, 'Courier New', Courier, mono, serif;"> </span>
           
/* bb.c  */
#include <stdio.h>
double global ; /* weak symbol */
void bb()
{
        printf("global = %f, sizeof(global) in bb = %d\n", global, sizeof(global));
}
           

編譯成目标檔案:

gcc -c aa.c bb.c
           

得到aa.o 和 bb.o兩個目标檔案

來看看他們的符号表

C語言中的強符号與弱符号一、概述二、哪些符号是弱符号?三、如何避免呢?補充:
C語言中的強符号與弱符号一、概述二、哪些符号是弱符号?三、如何避免呢?補充:

可以清楚的看到,在兩個目标檔案中,Ndx數值都是COM,表示此時它們被放在COMMON塊。在aa.o中global的大小是4個位元組,在bb.o中global的大小是8個位元組。

那麼這兩個目标檔案連結生成可執行檔案後,global的大小是多少呢? -- 當不同的目标檔案需要的COMMON塊空間大小不一緻時,以最大的那塊為準。

gcc -c aa.c bb.c
           

得到可執行檔案cc

C語言中的強符号與弱符号一、概述二、哪些符号是弱符号?三、如何避免呢?補充:

果然,global最終的大小為8個位元組。

C語言中的強符号與弱符号一、概述二、哪些符号是弱符号?三、如何避免呢?補充:

 是以總體來看,未初始化全局變量最終還是被放在BSS段的。

如果我們給aa.c中的global指派把它變成強符号呢?如下:

/* aa.c  */
#include <stdio.h>
int global = 100;       /* strong symbol */
int main(int argc, const char *argv[])
{
        printf("global = %d, sizeof(global) in main = %d\n", global, sizeof(global));
        bb();
        return 0;
}
           
/* bb.c  */
#include <stdio.h>
double global;  /* weak symbol */
void bb()
{
        printf("global = %f, sizeof(global) in bb = %d\n", global, sizeof(global));
}
           

得到兩個目标檔案後檢視符号,aa.o中global放在.data段,bb.o依然放在COMMON塊,最終的cc中global大小4位元組,這很好的驗證了本文一開始的第二條規則。

可是有例外情況,看下面程式:

/* aa.c  */
#include <stdio.h>
int global;     /* weak symbol */
int main(int argc, const char *argv[])
{
        printf("global = %d, sizeof(global) in main = %d\n", global, sizeof(global));
        bb();
        return 0;
}
           
/* bb.c  */
#include <stdio.h>
double __attribute__ ((weak)) global = 1.0;     /* weak symbol */
void bb()
{
        printf("global = %f, sizeof(global) in bb = %d\n", global, sizeof(global));
}
           

aa.c和bb.c中global都是弱符号,如果按照上面的規則的話,最終的可執行檔案中global的大小應該是8個位元組,可惜結果并不是我們所期望的:

C語言中的強符号與弱符号一、概述二、哪些符号是弱符号?三、如何避免呢?補充:

看到沒,最終的可執行檔案cc中global所占記憶體卻是4個位元組!為什麼? 下面是我在ELF文檔裡找到的一段:

Global and weak symbols differ in two major ways.
(全局符号和弱符号的差別主要在兩個方面。)
When the link editor combines several relocatable object files, it does not allow multiple definitions of STB_GLOBAL symbols with the same name. On the other hand, if a defined global symbol exists, the appearance of a weak symbol with the same name will not cause an error. The link editor honors the global definition and ignores the weak ones. Similarly, if a common symbol exists (i.e., a symbol whose st_shndx field holds SHN_COMMON), the appearance of a weak symbol with the same name will not cause an error. The link editor honors the common definition and ignores the weak ones.
(* 當連結器連結幾個可重定位的目标檔案時,它不允許具有STB_GLOBAL屬性的符号以相同名字進行重複定義。另一方面,如果一個已定義的全局符号存在,則即便另一個具有相同名字的弱符号存在也不會引起錯誤。連結器将認可全局符号的定義而忽略弱符号的定義。與此相似,如果一個符号被放在COMMON塊(就是說這個符号的 st_shndx 成員的值為SHN_COMMON),則一個同名的弱符号也不會引起錯誤。連結器同樣認可放在COMMON塊符号的定義而忽略其他的弱符号。)
           

至于為什麼這樣處理,目前我還不得而知,如果讀者知道的話,麻煩告訴我一下^_^!

再來看一種情況!如下:

/* aa.c  */
#include <stdio.h>
int __attribute__((weak)) global = 1;   /* weak symbol */
int main(int argc, const char *argv[])
{
        printf("global = %d, sizeof(global) in main = %d\n", global, sizeof(global));
        bb();
        return 0;
}
           
/* bb.c  */
#include <stdio.h>
double __attribute__((weak)) global = 1.0;      /* weak symbol */
void bb()
{
        printf("global = %f, sizeof(global) in bb = %d\n", global, sizeof(global));
}
           

結果卻是:

C語言中的強符号與弱符号一、概述二、哪些符号是弱符号?三、如何避免呢?補充:

 看到沒,同樣都是弱符号,卻因為編譯順序的不同,可執行檔案中的大小也不同,為什麼會這樣,目前我也是不得而知!

簡而言之,在目标檔案中沒有将未初始化的全局變量像未初始化的靜态變量那樣放在BSS段,而是放在COMMON塊,是因為現在的編譯器和連結器允許不同類型的弱符号存在,最本質的原因是連結器無法判斷各個符号的類型是否一緻。

有了COMMON塊之後就可以很好的解決這個問題了。

補充:

程式設計中我們可以使用GCC的“-fno-common”把所有的未初始化的全局變量不以COMMON塊的形式處理,也可以使用“__attribute__ ((nocommon))”,如下:

int global __attribute__ ((nocommon));  /* strong symbol */
           

一旦一個未初始化的全局變量不是以COMMON塊的形式存在,那麼它就相當于一強符号,如果其他目标檔案中還有同一個變量的強符号定義,連結時就會發生符号重複定義錯誤。

參考連結:

http://blog.focus-linux.net/?cat=14 (通過未初始化全局變量,研究BSS段和COMMON段的不同)

http://blog.copton.net/articles/linker/index.html ( C++ and the linker)

http://www.lurklurk.org/linkers/linkers.html ( Beginner's Guide to Linkers)

https://thunked.org/programming/code-obfuscation-with-linker-symbol-abuse-t100.html

轉載于:https://www.cnblogs.com/noble/p/4144162.html