最近在學習《程式設計師的自我修養——鏈結、裝載與庫》時,get到了乙個程式設計客棧新的知識點:弱符號與弱引用。書中簡短的介紹,讓我了解到弱符號的含義以及使用方式。了解我的朋友,應該知道我喜歡將知識點與我們實際工作結合起來,在工作中利用起來,正所謂學以善用。根據我的理解,覺得利用弱符號的特性可以幫組我們在工作中編寫出更加穩定,可復用,可組合的優秀**。在此向大家分享。
在編碼過程中,我們經常遇到符號重定義的錯誤。編譯器會報如下錯誤:
multiple definition of `***';
這就是符號重複定義導致的,再往細裡面說,是在同一作用域內符號衝突。我們知道變數是由作用域和生命週期概念的。比如:
例1:main.c
int strong=0;
int main()
strong.c
int strong=1;
gcc main.c strong.c -o main則會報重定義錯誤。因為在main.c 和strong.c 檔案中,整型變數strong是全域性變數,它們的作用域都是跨檔案的。若是在不同的作用域,即使相同變數名,也不會報錯。編譯器會有預設的優先順序處理:總是更小作用域的變數覆蓋更大作用域的變數,前提是這兩個變數的作用域是包含或被包含的關係。比如:
例2:main.c
int strong=0;
int main()
strong.c
static int strong=1;
gcc main.c strong.c -o main不再報錯。此時main.c 中的strong 變數的作用域是跨檔案,而strong.c中的strong變數的作用域僅限strong.c檔案。因此不存在相同作用域中,符號重定義問題。並且結果輸出為0;
同理,下面的**會編譯報錯嗎?輸出為多少呢?
main.c
int strong=0;
int main()
strong.c
static int strong=1;
甚至於下面的**也是合法的:
main.c
int strong=0;
int main()
return 0;
}在c語言中,我們可以簡單地認為花括號是檔案內作用域的分隔符。
編譯器預設函式和已初始化的全域性變數為強符號,而未初始化的全域性變數為弱符號;同時開發者可以通過"attribute((weak))"來宣告乙個符號為弱符號;
gcc 在編譯過程中,對於強弱符號遵循一定規則進行取捨:
很明顯,例1 則是出現多個強符號,導致的redefinition 錯誤。例如下面code:
main.c
int strong;
int strong=2;
int main()
編譯並不會出錯,並且輸出為2;
我們知道在編譯成可執行檔案時,若原始檔引用了外部目標檔案的符號,在鏈結過程中,需要找到對應的符號定義,若未找到對應符號(未定義),鏈結器會報符號位未定義錯誤,導致編譯出錯。這種被稱為強引用。與相對應的時弱引用(開發者可通過attribute((weakref))宣告),鏈結器在鏈結符號過程中,若發現符號為弱引用,即使沒有找到符號定義,鏈結時也不會報錯,但是會將該引用預設為0;
書中的**如下:
main.c
int strong;
int strong=2;
int main()
雖然沒有定義foo(),但是我們可以將它編譯成可執行檔案,並且gcc 編譯不會報鏈結錯誤,但是當我們執行時,會發生執行錯誤。實際上新版本的編譯器上訴的**會在鏈結時報錯的,新版本的示例**應該如下:(新版本的weakref 需要函式別名,且必須是static 修飾)
main.c
static __attribute__((weakref("foo"))) void myfoo(void);
void main(void)
}新版的弱符號引用如上所示。即表示若沒有找到foo函式,編譯不報錯,但是會預設myfoo為null。若在其他庫中定義了foo函式,對myfoo的引用就相當於對foo的引用。這種弱引用在庫的使用上十分有用的。
經過上面的描述,我們了解到了強符號,弱符號,強引用,弱引用的概念。我認為起碼有兩點特性可以在我們工作中使用:
一些庫中對外介面可以宣告為弱符號。比如:
在math庫中,我們發現add(int num1, int num2)這個介面存在問題,那我們解決方式一般有以下幾種:
1. 實現乙個myadd(int num1,int num2)介面,之後再將專案中的所有add,替換為myadd。這種方式可行,但是存在缺點:修改量大,並且後續人員不清楚背景,很有可能繼續使用熟悉的add介面。
2. 更新math庫,從更本解決此問題。這種方式比較推薦。但是也並不是通用的,比如有些庫並不是開源的,並且已經過了支援日期,也就不適用了。
此時,我們可以自己在專案中定義乙個add(int num1,int num2)介面,用強符號替換庫中的弱符號,這樣改動是比較小的。(這種情景需要了解介面的實現內容,可給呼叫者較高的重構權力)
應用層的開發,離不開sdk的提供,一般sdk維護了,即使應用沒有需求發生,往往也會為了配合sdk,進行簡單的修改。以裝置公升級作為舉例,若公升級過程中,分為傳包(pass),驗籤(verify),解密(decode),安裝(install),上傳日誌(report)等步驟,並且這些核心介面都是以libsdk.so庫的形式提供給應用工程師。那麼正常情況下,應用邏輯大致如下:
使用者業務流程
...pass();
...verify();
...decode();
...install();
...report();
...但是這樣的業務**,我覺得是非常差的。比如新的專案中,不需要做解密包操作了,(理論上libsdk.so庫中應該不具備decode介面了),這樣就會導致應用程式編譯失敗。undefine 'decode'。
因此我建議應用**可以如下:
static __attribute__((weakref("pass"))) void mypass(void);
static __attribute__((weakref("verify"))) void myverify(void);
static __attribute__((weakref("decode"))) void mydecode(void);
static __attribute__((weakref("install"))) void myinstall(void);
static __attribute__((weakref("report"))) void myreport(void);
使用者業務流程
...if(mypass)
mypass();
else
printf("don't need pass\n");
...if(myverify)
myverify();
else
printf("don't need verify\n");
...if(mydecode)
mydecode();
else
printf("don't need decode\n");
...if(myinstall)
myinstall();
else
printf("don't need install\n");
...if(myreport)
myreport();
else
printf("don't need report\n");
...以上便是我理解的內容,希望能引起的你的共鳴,如果你有好的想法,也可以和我一起分享。。。
關於C語言中的強符號 弱符號 強引用和弱引用
首先我表示很悲劇,在看 程式設計師的自我修養 鏈結 裝載與庫 之前我竟不知道c有強符號 弱符號 強引用和弱引用。在看到3.5.5節弱符號和強符號時,我感覺有些困惑,所以寫下此篇,希望能和同樣感覺的朋友交流也希望高人指點。首先我們看一下書中關於它們的定義。引入場景 1 檔案a中定義並初始化變數i in...
弱符號與強符號,弱引用與強引用
對c c 而言,編譯器預設函式和初始化了的全域性變數為強符號。未初始化的全域性變數為弱符號。此處弱符號與強符號均是針對定義來說的,不是針對符號的引用。也可以通過gcc的 attribute weak 來定義任何乙個強符號為弱符號。cpp view plain copy extern in ext i...
C語言中的強符號與弱符號
參考 程式設計師的自我修養 參考 c語言中的強符號與弱符號 main.c int a 100 int main other.c int a 10 編譯 gcc main.c other.c編譯結果 ld 1 duplicate symbol for architecture x86 64 clang...