您好,登錄后才能下訂單哦!
這篇文章主要講解了“如何理解函數或全局變量重復定義”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“如何理解函數或全局變量重復定義”吧!
可能有些朋友第一反應是,那肯定是編譯不過嘍:
// 來源:公眾號【編程珠璣】 // 作者:守望先生 // fun.c #include<stdio.h> void fun() { printf("編程珠璣\n"); } // main.c #include<stdio.h> void func() { printf("公眾號\n"); } int main(void) { func(); return 0; }
編譯:
$ gcc -o main main.c fun.c /tmp/ccKeACRk.o: In function `fun': fun.c:(.text+0x0): multiple definition of `fun' /tmp/cc4ezgqh.o:main.c:(.text+0x0): first defined here collect2: error: ld returned 1 exit status
可以看到這里報錯了,因為fun重復定義了。
但是重復定義就會報錯,會編譯不過嗎?不全是!
再看下面的代碼:
// 來源:公眾號【編程珠璣】 // 作者:守望先生 //var.c int num; void change() { num = 1023; } //main.c #include<stdio.h> void change(); int num = 1024; int main(void) { printf("before:num is %d\n", num); change(); printf("after:num is %d\n", num); return 0; }
輸出結果:
before:num is 1024 after:num is 1023
從結果中可以看到,雖然num被定義了兩次,但是仍然可以編譯通過,并且正常運行。這又是為什么呢?
符號
在說明今天重點分享的內容之前,先簡單了解一下什么是符號。在《hello程序是如何變成可執行文件的》中講到過,ELF文件生成的最后階段會經歷鏈接,而鏈接階段正是基于符號才能完成。每個目標文件都會有一個符號表。而鏈接過程正是通過符號表中的符號,將不同的目標文件“粘”在一起,形成最后的庫或者可執行文件。要查看一個目標文件的符號信息也很容易:
// symbol.c #include<stdio.h> int symbol = 1024; int func_symbol() { return 0; }
編譯:
$ gcc smbol.c #編譯 $ nm symbol.o #查看符號信息 0000000000000000 T func_symbol 0000000000000000 D symbol
通過nm命令就可以查看符號信息,這里就有我們的func_symbol函數和全局變量symbol的符號。
關于nm的使用,在《幾個命令了解ELF文件的秘密》也有介紹。
除了上面提到的全局符號,目標文件中還有其他符號信息,不過這不是本文關注的重點。
強符號與弱符號
對于C/C++語言來說,編譯器默認函數和初始化了的全局變量為強符號,未初始化的全局變量為弱符號。當然也可以通過
__attribute__((weak))
來定義一個強符號為弱符號。
通過下面的例子來看看哪些是強符號,哪些是弱符號:
#include<stdio.h> int weak; // 未初始化全局變量,弱符號 int strong = 1024; // 已初始化全局變量,強符號 __attribute__((weak)) int weak1 = 2222; // 使用標識修飾的弱符號 int main(void) { printf("編程珠璣\n"); return 0; }
注意,這里的強符號與弱符號都是針對定義來說的。
同名時,用哪個?
對于多重定義,即標題提到的變量重名時,鏈接器有它的處理規則:
1.強符號不允許重復
2.有一個強符號和多個弱符號,使用強符號
3.多個弱符號,則隨意選擇一個
關于第一點,在最開始的例子中你已經見到了,最常見的情況就是你重復定義了變量或者函數等等。
而第二點也有示例,示例中,雖然定義了兩個num,但是var.c中未初始化的num是弱符號,main.c中的num是強符號,這種情況下編譯正常。只是最終會使用強符號的num。
再看一個第三點的例子也是類似,當其中main.c的num無初始化時,也是可以編譯過的。這種情況下的誤用也就罷了,如果是重復的符號,但是類型不同,問題就更大了,即var.c的內容如下:
//var.c double num; void change() { num = 1023; }
這里的num變成了double,再次編譯運行,你會發現意想不到的結果:
before:num is 1024 after:num is 0
為什么修改后是0?原因在于double類型的數據存儲與int類型數據存儲格式不一樣,且它們占用空間長度都不一樣,在本文例子中,double占用8字節,而int占用4字節。
總之,這不是我們想要的結果,最終的后果可能比我們想象的要嚴重,要更難發現。
感謝各位的閱讀,以上就是“如何理解函數或全局變量重復定義”的內容了,經過本文的學習后,相信大家對如何理解函數或全局變量重復定義這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。