強符號與弱符號

咱們常常在編程中碰到一種狀況叫符號重複定義。多個目標文件中含有相同名字全局符號的定義,那麼這些目標文件連接的時候將會出現符號重複定義的錯誤。好比咱們在目標文件A和目標文件B都定義了一個全局×××變量global,並將它們都初始化,那麼連接器將A和B進行連接時會報錯:
 
b.o:(.data+0x0): multiple definition of `global'
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"既非強符號也非弱符號,由於它是一個外部變量的引用。針對強弱符號的概念,連接器就會按以下規則處理與選擇被屢次定義的全局符號:
規則1:不容許強符號被屢次定義(即不一樣的目標文件中不能有同名的強符號);若是有多個強符號定義,則連接器報符號重複定義錯誤。
規則2:若是一個符號在某個目標文件中是強符號,在其餘文件中都是弱符號,那麼選擇強符號。
規則3:若是一個符號在全部目標文件中都是弱符號,那麼選擇其中佔用空間最大的一個。好比目標文件A定義全局變量global爲int型,佔4個字節;目標文件B定義global爲double型,佔8個字節,那麼目標文件A和B連接後,符號global佔8個字節(儘可能不要使用多個不一樣類型的弱符號,不然容易致使很難發現的程序錯誤)。
弱引用和強引用  目前咱們所看到的對外部目標文件的符號引用在目標文件被最終連接成可執行文件時,它們需要被正確決議,若是沒有找到該符號的定義,連接器就會報符號未定義錯誤,這種被稱爲強引用(Strong Reference)。與之相對應還有一種弱引用(Weak Reference),在處理弱引用時,若是該符號有定義,則連接器將該符號的引用決議;若是該符號未被定義,則連接器對於該引用不報錯。連接器處理強引用和弱引用的過程幾乎同樣,只是對於未定義的弱引用,連接器不認爲它是一個錯誤。通常對於未定義的弱引用,連接器默認其爲0,或者是一個特殊的值,以便於程序代碼可以識別。弱引用和弱符號主要用於庫的連接過程,咱們將在"庫"這一章再來詳細講述。弱符號跟連接器的COMMON塊概念聯繫很緊密,咱們在後面"深刻靜態連接"這一章中的"COMMON塊"一節還會回顧弱符號的概念。
在GCC中,咱們能夠經過使用"__attribute__((weakref))"這個擴展關鍵字來聲明對一個外部函數的引用爲弱引用,好比下面這段代碼:
__attribute__ ((weakref)) void foo();
int main()
{
foo();
}
咱們能夠將它編譯成一個可執行文件,GCC並不會報連接錯誤。可是當咱們運行這個可執行文件時,會發生運行錯誤。由於當main函數試圖調用foo函數時,foo函數的地址爲0,因而發生了非法地址訪問的錯誤。一個改進的例子是:
__attribute__ ((weakref)) void foo();
int main()
{
if(foo) foo();
}
這種弱符號和弱引用對於庫來講十分有用,好比庫中定義的弱符號能夠被用戶定義的強符號所覆蓋,從而使得程序能夠使用自定義版本的庫函數;或者程序能夠對某些擴展功能模塊的引用定義爲弱引用,當咱們將擴展模塊與程序連接在一塊兒時,功能模塊就能夠正常使用;若是咱們去掉了某些功能模塊,那麼程序也能夠正常連接,只是缺乏了相應的功能,這使得程序的功能更加容易裁剪和組合。
在Linux程序的設計中,若是一個程序被設計成能夠支持單線程或多線程的模式,就能夠經過弱引用的方法來判斷當前的程序是連接到了單線程的Glibc庫仍是多線程的Glibc庫(是否在編譯時有-lpthread選項),從而執行單線程版本的程序或多線程版本的程序。咱們能夠在程序中定義一個pthread_create函數的弱引用,而後程序在運行時動態判斷是否連接到pthread庫從而決定執行多線程版本仍是單線程版本:
#include <stdio.h>
#include <pthread.h>
int pthread_create(
pthread_t*,
const pthread_attr_t*,
void* (*)(void*),
void*) __attribute__ ((weak));
int main()
{
if(pthread_create) {
printf("This is multi-thread version!\n");
// run the multi-thread version
// main_multi_thread()
} else {
printf("This is single-thread version!\n");   
// run the single-thread version
// main_single_thread()
}
}
編譯運行結果以下:
$ gcc pthread.c -o pt$ ./ptThis is single-thread version!$ gcc pthread.c -lpthread -o pt$ ./ptThis is multi-thread version!
相關文章
相關標籤/搜索