溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何理解函數(shù)或全局變量重復(fù)定義

發(fā)布時(shí)間:2021-10-18 15:21:05 來(lái)源:億速云 閱讀:131 作者:iii 欄目:web開(kāi)發(fā)

這篇文章主要講解了“如何理解函數(shù)或全局變量重復(fù)定義”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“如何理解函數(shù)或全局變量重復(fù)定義”吧!

可能有些朋友第一反應(yīng)是,那肯定是編譯不過(guò)嘍:

// 來(lái)源:公眾號(hào)【編程珠璣】 // 作者:守望先生 // fun.c #include<stdio.h> void fun() {     printf("編程珠璣\n"); }  // main.c #include<stdio.h> void func() {     printf("公眾號(hào)\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

可以看到這里報(bào)錯(cuò)了,因?yàn)閒un重復(fù)定義了。

但是重復(fù)定義就會(huì)報(bào)錯(cuò),會(huì)編譯不過(guò)嗎?不全是!

再看下面的代碼:

// 來(lái)源:公眾號(hào)【編程珠璣】 // 作者:守望先生 //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; }

輸出結(jié)果:

before:num is 1024  after:num is 1023

從結(jié)果中可以看到,雖然num被定義了兩次,但是仍然可以編譯通過(guò),并且正常運(yùn)行。這又是為什么呢?

符號(hào)

在說(shuō)明今天重點(diǎn)分享的內(nèi)容之前,先簡(jiǎn)單了解一下什么是符號(hào)。在《hello程序是如何變成可執(zhí)行文件的》中講到過(guò),ELF文件生成的最后階段會(huì)經(jīng)歷鏈接,而鏈接階段正是基于符號(hào)才能完成。每個(gè)目標(biāo)文件都會(huì)有一個(gè)符號(hào)表。而鏈接過(guò)程正是通過(guò)符號(hào)表中的符號(hào),將不同的目標(biāo)文件“粘”在一起,形成最后的庫(kù)或者可執(zhí)行文件。要查看一個(gè)目標(biāo)文件的符號(hào)信息也很容易:

// symbol.c #include<stdio.h> int symbol = 1024; int func_symbol() {     return 0; }

編譯:

$ gcc smbol.c #編譯 $ nm symbol.o #查看符號(hào)信息 0000000000000000 T func_symbol 0000000000000000 D symbol

通過(guò)nm命令就可以查看符號(hào)信息,這里就有我們的func_symbol函數(shù)和全局變量symbol的符號(hào)。

關(guān)于nm的使用,在《幾個(gè)命令了解ELF文件的秘密》也有介紹。

除了上面提到的全局符號(hào),目標(biāo)文件中還有其他符號(hào)信息,不過(guò)這不是本文關(guān)注的重點(diǎn)。

強(qiáng)符號(hào)與弱符號(hào)

對(duì)于C/C++語(yǔ)言來(lái)說(shuō),編譯器默認(rèn)函數(shù)和初始化了的全局變量為強(qiáng)符號(hào),未初始化的全局變量為弱符號(hào)。當(dāng)然也可以通過(guò)

__attribute__((weak))

來(lái)定義一個(gè)強(qiáng)符號(hào)為弱符號(hào)。

通過(guò)下面的例子來(lái)看看哪些是強(qiáng)符號(hào),哪些是弱符號(hào):

#include<stdio.h> int weak; // 未初始化全局變量,弱符號(hào) int strong = 1024; // 已初始化全局變量,強(qiáng)符號(hào) __attribute__((weak)) int weak1 = 2222; // 使用標(biāo)識(shí)修飾的弱符號(hào) int main(void) {     printf("編程珠璣\n");     return 0; }

注意,這里的強(qiáng)符號(hào)與弱符號(hào)都是針對(duì)定義來(lái)說(shuō)的。

同名時(shí),用哪個(gè)?

對(duì)于多重定義,即標(biāo)題提到的變量重名時(shí),鏈接器有它的處理規(guī)則:

  • 1.強(qiáng)符號(hào)不允許重復(fù)

  • 2.有一個(gè)強(qiáng)符號(hào)和多個(gè)弱符號(hào),使用強(qiáng)符號(hào)

  • 3.多個(gè)弱符號(hào),則隨意選擇一個(gè)

關(guān)于第一點(diǎn),在最開(kāi)始的例子中你已經(jīng)見(jiàn)到了,最常見(jiàn)的情況就是你重復(fù)定義了變量或者函數(shù)等等。

而第二點(diǎn)也有示例,示例中,雖然定義了兩個(gè)num,但是var.c中未初始化的num是弱符號(hào),main.c中的num是強(qiáng)符號(hào),這種情況下編譯正常。只是最終會(huì)使用強(qiáng)符號(hào)的num。

再看一個(gè)第三點(diǎn)的例子也是類似,當(dāng)其中main.c的num無(wú)初始化時(shí),也是可以編譯過(guò)的。這種情況下的誤用也就罷了,如果是重復(fù)的符號(hào),但是類型不同,問(wèn)題就更大了,即var.c的內(nèi)容如下:

//var.c double num; void change() {     num = 1023; }

這里的num變成了double,再次編譯運(yùn)行,你會(huì)發(fā)現(xiàn)意想不到的結(jié)果:

before:num is 1024  after:num is 0

為什么修改后是0?原因在于double類型的數(shù)據(jù)存儲(chǔ)與int類型數(shù)據(jù)存儲(chǔ)格式不一樣,且它們占用空間長(zhǎng)度都不一樣,在本文例子中,double占用8字節(jié),而int占用4字節(jié)。

總之,這不是我們想要的結(jié)果,最終的后果可能比我們想象的要嚴(yán)重,要更難發(fā)現(xiàn)。

感謝各位的閱讀,以上就是“如何理解函數(shù)或全局變量重復(fù)定義”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)如何理解函數(shù)或全局變量重復(fù)定義這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI