天天看點

c語言程式中weak,淺談C語言中的強符号、弱符号、強引用和弱引用

首先我表示很悲劇,在看《程式員的自我修養--連結、裝載與庫》之前我竟不知道C有強符号、弱符号、強引用和弱引用。在看到3.5.5節弱符号和強符号時,我感覺有些困惑,是以寫下此篇,希望能和同樣感覺的朋友交流也希望高人指點。

首先我們看一下書中關于它們的定義。

引入場景:(1)檔案A中定義并初始化變量i(int i = 1), 檔案B中定義并初始化變量i(int i = 2)。編譯連結A、B時會報錯b.o:(.data+0x0): multiple definition of `i';a.o:(.data+0x0): multiple definition of `i'。(2)在檔案C中定義并初始化兩個變量i(int i = 1; int i = 2), 編譯連結時會報錯c.c:2:5: error: redefinition of ‘i'; c.c:1:5: note: previous definition of ‘i' was here。

強符号:像場景中這樣的符号定義被稱為強符号,對于C/C++來說,編譯器預設函數和初始化的全局變量為強符号。

弱符号:接上文,為初始化的全局變量為弱符号。

編譯器關于強弱符号的規則有:(1)強符号不允許多次定義,但強弱可以共存;(2)強弱共存時,強覆寫弱;(3)都是弱符号時,選擇占用空間最大的,如選擇  double類型的而不選擇int類型的。

由以上定義是以有我之前沒有想到的場景:

代碼a.c:

1 int i = 2;

代碼b.c:

#include

int i;

int main(int argc, char** argv)

{

printf("i = %d\n", i);

return 0;

}

編譯檔案a和b并連結,結果輸出i為2而不是0。

并且在同一個檔案中定義但未初始化兩個相同的變量不會報錯,隻有在使用變量時才會報錯。

對于GCC編譯器來說,還允許使用__attribute__((weak))來将強符号定義為弱符号,所已有

代碼c.c

#include

__attribute__((weak)) int i = 1;

int main(int argc, char** argv)

{

printf("i = %d\n", i);

return 0;

}

結果i的輸出仍未2而不是1。

那麼對于函數而言是不是也這樣呢?先不看函數,而是先看由強弱符号而進一步引入的強弱引用。書中關于強弱引用的概述是對于強引用若未定義則連結時肯定會報錯,而對于弱引用則不會報錯,連結器預設其為0(這一點對于函數好了解,即函數符号所代表入口位址為0;對于變量就要注意了,既然是引用那自然就是位址了,是以同函數一樣變量的位址為0而不是變量的值為0)。此時對于強弱引用是不是還沒有什麼明确的概念呢?到底什麼是引用?引用和符号又是什麼關系?這裡我說一下我的了解(歡迎指正),在定義和聲明處指定的函數名、變量名即為對應的符号,而在代碼其他處調用函數或使用變量時,則把函說明和變量名看作引用,這樣一來符号和引用在代碼層面上其實就是一個東西,隻是根據環境而叫法不同而已。那麼強符号對應強引用,弱符号對應弱引用。

有上面的強弱引用的特點可看出,當一個函數為弱引用時,不管這個函數有沒有定義,連結時都不會報錯,而且我們可以根據判斷函數名是否為0來決定是否執行這個函數。這樣一來,包含這些函數的庫就可以以子產品、插件的形式和我們的引用組合一起,友善使用和解除安裝,并且由于強符号可以覆寫弱符号和強弱符号與強弱引用的關系可知,我們自己定義函數可以覆寫庫中的函數,多麼美妙。

先看根據條件判斷是否執行函數:

代碼d.c

#include

void func()

{

printf("func()#1\n");

}

代碼e.c

#include

__attribute__((weak)) void func();

int main(int argc, char** argv)

{

if (func)

func();

return 0;

}

編譯d.c,cc -c d.c 輸出d.o;編譯e.c并連結d.o,cc d.o e.c -o e輸出可執行檔案e,運作e正常執行函數func。編譯e.c但不連結d.o,此時并不會報錯,隻不過func不會執行,因為沒有它的定義是以if(func)為假。

再看函數覆寫:

代碼f.c

#include

__attribute__((weak)) void func()

{

printf("func()#1\n");

}

代碼g.c

#include

void func()

{

printf("func()#2\n");

}

int main(int argc, char** argv)

{

func();

return 0;

}

~

編譯連結,結構輸出"func()#2"。

以上可以說明函數和變量是保持一緻的,其實對應變量也可以像使用函數那樣先判斷再使用,隻不過不是判斷變量的值而是變量的位址,如

代碼v1.c

int i = 2;

代碼v2.c

#include

__attribute__((weak)) extern int i;

int main(int argc, char** argv)

{

if (&i)

printf("i = %d\n", i);

return 0;

}

~

編譯并連結v1時,輸出2;編譯但不連結v1時無輸出。這樣做時要厘清定義和聲明的差別,__attribute__((weak)) int i 是定義變量并轉換為弱符号,這樣i是配置設定了空間的,而__attribute__((weak)) extern int i 則将原來定義的變量i由強符号轉換為弱符号,導緻使用i時不是強引用而是弱引用。不過雖然變量可以這麼做但沒有函數那樣有意義。

上面關于強弱引用仍舊使用的是GCC提供的__attribute__((weak)),而書中還提到了__attribute__((weakref)),後者貌似更能展現“引用”這一關鍵詞。而我之是以使用前者來介紹強弱引用,是因為我對關于強弱符号與強弱引用對應關系的了解。關于__attribute__((weakref))的使用方法,這裡介紹一種(兩者都有不同的使用方法)。

代碼a.c

#include

void bar()

{

printf("foo()\n");

}

代碼b.c

#include

static void foo() __attribute__((weakref("bar")));

int main(int argc, char** argv)

{

if (foo)

foo();

return 0;

}

注意函數foo的static修飾符,沒有的話會報錯,這樣将函數foo限制在隻有本檔案内可使用。

好了,夜已深,寫的有點淩亂,我也淩亂了。