舉例講解C語言鏈接器的符號解析機(jī)制
1. 符號分類
(1)全局符號:非靜態(tài)全局變量,非靜態(tài)函數(shù)
(2)外部符號:定義于其它模塊,而被本模塊引用的全局變量和函數(shù)
(3)本地符號:靜態(tài)變量(包括全局和局部),靜態(tài)函數(shù)
對于靜態(tài)局部變量,編譯器會為其生成唯一的名字。如x.fun1,x.fun2。本地符號對鏈接器來說是不可見的。
2. 符號決議
當(dāng)編譯器遇到一個不是本模塊定義的符號時,會假設(shè)該函數(shù)由其它模塊定義,并生成一個鏈接器符號表?xiàng)l目,交由鏈接器處理。如果鏈接器在它的任何輸入模塊都沒有找到該符號,會給出一個類似undefined reference to 'xxx'的鏈接錯誤。而如果鏈接器在輸入模塊中找到了一個以上的外部符號定義,這個時候就需要鏈接器進(jìn)行符號決議,鏈接器對多個外部符號定義可能并不報錯甚至警告,而是按照它的規(guī)則去選擇其中一個符號定義。
鏈接器將各個模塊輸出的全局符號,分類為強(qiáng)符號和弱符號:
(1)強(qiáng)符號:函數(shù)和已初始化的全局變量
(2)弱符號:為初始化全局變量
根據(jù)強(qiáng)弱符號的定義,鏈接器按照下面的規(guī)則處理多重定義的符號:
規(guī)則1:不允許有多個強(qiáng)符號定義
規(guī)則2:如果有一個強(qiáng)符號和多個弱符號,那么選擇強(qiáng)符號
規(guī)則3:如果有多個弱符號,那么從這些弱符號中選擇sizeof大的那個,如果大小相同,則選擇先鏈接的那個
上面的規(guī)則是很多鏈接錯誤的根源,因?yàn)榫幾g器在決議時可能默默地替你作出了決定,你并不知曉。根據(jù)上面的規(guī)則,可以引出下面幾個經(jīng)典例子:
例1:
// in lib1.c int x; void f() { x = 1235; } // in main1.c #include<stdio.h> void f(void); int x = 1234; int main(void) { f(); printf("x=%d\n", x); return 0; }
上面的代碼中,main函數(shù)printf輸出: x=1235。因?yàn)殒溄悠魍ㄟ^規(guī)則2決議符號x的定義為main.c中的強(qiáng)符號定義,而lib.c的作者并不知情,他對x的使用和修改影響到了main.c。這種交互修改,相互影響將會很復(fù)雜,因?yàn)榇蠹叶家詾樽约涸谧鰧Φ氖虑椋谟脤Φ淖兞?。而整個決議過程,鏈接器悄無聲息地完成了。
例2:
// in lib2.c double x; void f() { x = -0.0; } // in main2.c #include<stdio.h> void f(void); int x = 1234; int y = 1235; int main() { f(); printf("x=0x%x y=0x%x \n", x, y); return 0; }
這種情況下,程序得到輸出: x=0x0 y=0x80000000,而鏈接器(gcc ld)也終于給出一條警告:
ld: warning: tentative definition of '_x' with size 8 from 'obj/Debug/lib2.o' is being replaced by real definition of smaller size 4 from 'obj/Debug/main2.o'
鏈接器決議的是符號地址,而相鄰的全局變量可能在.data段中的內(nèi)存地址也相鄰,因此也就引發(fā)了更復(fù)雜的問題。這一點(diǎn)和棧溢出很像,但是比棧溢出更復(fù)雜,因?yàn)閱栴}出在多個模塊之間,而不是在一個函數(shù)內(nèi)部。
例3:
// in lib3.c struct { int a; int b; } x; void f() { x.a = 123; x.b = 456; printf("in f(): sizeof(x)=%d, (&x)=0x%08x\n", sizeof(x), &x); } // in main3.c #include<stdio.h> void f(void); int x; int y; int main() { f(); printf("in main(): sizeof(x)=%d, (&x)=0x%08x, (&x)=0x%08x, x=%d,y=%d \n", sizeof(x), &x, &y, x, y); return 0; }
程序輸出:
in f(): sizeof(x)=8, (&x)=0x02489018 in main(): sizeof(x)=4, (&x)=0x02489018, (&y)=0x02489020, x=123,y=0
始終記住,外部符號決議的是地址,因此無論lib3.c和main3.c中,符號x地址都是唯一的,無論其被定義了幾次。其次sizeof是編譯器決議,與鏈接無關(guān),編譯器只看得到本模塊的定義或聲明。最后,由于符號x決議到lib3.c中的x,其size是8,因此main3.c中的y的地址比x大8,這是由鏈接器將lib3.o和main3.o合并后填入可執(zhí)行文件的.data段的。因此y是無關(guān)變量,被初始化為0,注意和例2的區(qū)別。
3. 總結(jié)
由于符號決議容易引發(fā)的種種問題,我們在寫C的時候應(yīng)注意:
盡量用static屬性隱藏變量和函數(shù)在模塊內(nèi)的聲明,就像在C++中盡量用private保護(hù)類私有成員一樣。
少定義弱符號,盡量初始化全局變量,這樣鏈接器會根據(jù)規(guī)則1給出多個符號定義的錯誤。
為鏈接器設(shè)置必要選項(xiàng),如gcc的 -fno-common,這樣在遇到多重符號定義時,鏈接器會給出警告。
4. C++的符號決議
C++并不支持強(qiáng)弱符號同時存在,所有符號都只能有一個定義(函數(shù)重載通過改寫函數(shù)符號來確保其唯一),因此在很大程度上避免了C中的鏈接器困擾。
上一篇:C語言使用DP動態(tài)規(guī)劃思想解最大K乘積與乘積最大問題
欄 目:C語言
下一篇:C語言中實(shí)現(xiàn)KMP算法的實(shí)例講解
本文標(biāo)題:舉例講解C語言鏈接器的符號解析機(jī)制
本文地址:http://mengdiqiu.com.cn/a1/Cyuyan/2257.html
您可能感興趣的文章
- 04-02c語言函數(shù)調(diào)用后清空內(nèi)存 c語言調(diào)用函數(shù)刪除字符
- 04-02c語言的正則匹配函數(shù) c語言正則表達(dá)式函數(shù)庫
- 04-02func函數(shù)+在C語言 func函數(shù)在c語言中
- 04-02c語言中對數(shù)函數(shù)的表達(dá)式 c語言中對數(shù)怎么表達(dá)
- 04-02c語言用函數(shù)寫分段 用c語言表示分段函數(shù)
- 04-02c語言編寫函數(shù)冒泡排序 c語言冒泡排序法函數(shù)
- 04-02c語言沒有round函數(shù) round c語言
- 04-02c語言分段函數(shù)怎么求 用c語言求分段函數(shù)
- 04-02C語言中怎么打出三角函數(shù) c語言中怎么打出三角函數(shù)的值
- 04-02c語言調(diào)用函數(shù)求fibo C語言調(diào)用函數(shù)求階乘


閱讀排行
本欄相關(guān)
- 04-02c語言函數(shù)調(diào)用后清空內(nèi)存 c語言調(diào)用
- 04-02func函數(shù)+在C語言 func函數(shù)在c語言中
- 04-02c語言的正則匹配函數(shù) c語言正則表達(dá)
- 04-02c語言用函數(shù)寫分段 用c語言表示分段
- 04-02c語言中對數(shù)函數(shù)的表達(dá)式 c語言中對
- 04-02c語言編寫函數(shù)冒泡排序 c語言冒泡排
- 04-02c語言沒有round函數(shù) round c語言
- 04-02c語言分段函數(shù)怎么求 用c語言求分段
- 04-02C語言中怎么打出三角函數(shù) c語言中怎
- 04-02c語言調(diào)用函數(shù)求fibo C語言調(diào)用函數(shù)求
隨機(jī)閱讀
- 08-05DEDE織夢data目錄下的sessions文件夾有什
- 01-10delphi制作wav文件的方法
- 01-10使用C語言求解撲克牌的順子及n個骰子
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 08-05織夢dedecms什么時候用欄目交叉功能?
- 04-02jquery與jsp,用jquery
- 01-11ajax實(shí)現(xiàn)頁面的局部加載
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 01-10C#中split用法實(shí)例總結(jié)