關於C語言中弱符號與弱引用的實際應用問題
最近在學習《程序員的自我修養——鏈接、裝載與庫》時,get到瞭一個新的知識點:弱符號與弱引用。書中簡短的介紹,讓我瞭解到弱符號的含義以及使用方式。瞭解我的朋友,應該知道我喜歡將知識點與我們實際工作結合起來,在工作中利用起來,正所謂學以善用。根據我的理解,覺得利用弱符號的特性可以幫組我們在工作中編寫出更加穩定,可復用,可組合的優秀代碼。在此向大傢分享。
符號重定義錯誤
在編碼過程中,我們經常遇到符號重定義的錯誤。編譯器會報如下錯誤:
multiple definition of `xxx’;
這就是符號重復定義導致的,再往細裡面說,是在同一作用域內符號沖突。我們知道變量是由作用域和生命周期概念的。比如:
例1: main.c int strong=0; int main() { printf("strong = %d\n",strong); return 0; } 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() { printf("strong = %d\n",strong); return 0; } 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() { int strong=2; printf("strong = %d\n",strong); return 0; } strong.c static int strong=1;
甚至於下面的代碼也是合法的:
main.c int strong=0; int main() { int strong=2; if(1) { int strong=3; } { int strong = 4; printf("strong = %d\n",strong); } return 0; }
在C語言中,我們可以簡單地認為花括號是文件內作用域的分隔符。
強符號與弱符號
編譯器默認函數和已初始化的全局變量為強符號,而未初始化的全局變量為弱符號;同時開發者可以通過”attribute((weak))“來聲明一個符號為弱符號;
gcc 在編譯過程中,對於強弱符號遵循一定規則進行取舍:
- 當有多個為強符號時,報”redefinition of ‘xxx'”錯誤
- 當僅有一個為強符號時,選取強符號的值
- 當都為弱符號時,選擇其中暫用空間較大的符號。(防止溢出越界等問題)—— 這個應該和編譯器有關,我在本地環境中,是不允許多個類型不同的弱符號存在的。編譯會出錯。
很明顯,例1 則是出現多個強符號,導致的redefinition 錯誤。例如下面code:
main.c int strong; int strong=2; int main() { printf("strong = %d\n",strong); return 0; }
編譯並不會出錯,並且輸出為2;
強引用與弱引用
我們知道在編譯成可執行文件時,若源文件引用瞭外部目標文件的符號,在鏈接過程中,需要找到對應的符號定義,若未找到對應符號(未定義),鏈接器會報符號位未定義錯誤,導致編譯出錯。這種被稱為強引用。與相對應的時弱引用(開發者可通過attribute((weakref))聲明),鏈接器在鏈接符號過程中,若發現符號為弱引用,即使沒有找到符號定義,鏈接時也不會報錯,但是會將該引用默認為0;
書中的代碼如下:
main.c int strong; int strong=2; int main() { printf("strong = %d\n",strong); return 0; }
雖然沒有定義foo(),但是我們可以將它編譯成可執行文件,並且GCC 編譯不會報鏈接錯誤,但是當我們運行時,會發生運行錯誤。實際上新版本的編譯器上訴的代碼會在鏈接時報錯的,新版本的示例代碼應該如下:(新版本的weakref 需要函數別名,且必須是static 修飾)
main.c static __attribute__((weakref("foo"))) void myfoo(void); void main(void) { if(myfoo) { myfoo(); } }
新版的弱符號引用如上所示。即表示若沒有找到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語言弱符號與弱引用內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- .Net報表開發控件XtraReport介紹
- 一文理解Android系統中強指針的實現
- 淺談Python類的單繼承相關知識
- 理解關於Android系統中輕量級指針的實現
- 一篇文章帶你瞭解C語言:入門基礎(2)