C語言?超詳細(xì)講解鏈接器
1 什么是鏈接器
典型的鏈接器把由編譯器或匯編器生成的若干個目標(biāo)模塊,整合成一個被稱為載入模塊或可執(zhí)行文件的實體–該實體能夠被操作系統(tǒng)直接執(zhí)行。
鏈接器通常把目標(biāo)模塊看成是由一組外部對象組成的。每個外部對象代表著機器內(nèi)存中的某個部分,并通過一個外部名稱來識別。因此,==程序中的每個函數(shù)和每個外部變量,如果沒有被聲明為static,就都是一個外部對象。==某些C編譯器會對靜態(tài)函數(shù)和靜態(tài)變量的名稱做一定改變,將它們也作為外部對象。由于經(jīng)過了“名稱修飾”,因此它們不會與其它源程序文件中的同名函數(shù)或同名變量發(fā)生命名沖突。
2 聲明與定義
extern int a;
上面的這段代碼并不是對a的定義,而是說明a是一個外部整型變量。
注意:引入之后,假如引入的位置在函數(shù)之外,就相當(dāng)于在那個位置定義了全局變量,同樣遵循局部變量優(yōu)先原則,如果引入位置在某個函數(shù)之內(nèi),就相當(dāng)于是一個局部變量,作用域與那個地方定義的局部變量相類似,此處討論聲明周期沒有任何意義。
int a; extern int a;
上面的這兩條語句既可以是在同一個源文件中,也可以位于程序的不同源文件之中。
==注意:每個外部變量只能定義一次。==如果外部變量的多個定義各指定一個初始值,例如:
int a = 7;
出現(xiàn)在一個源文件中,而
int a = 9;
出現(xiàn)在另一個源文件中,大多數(shù)系統(tǒng)都會拒絕接收該程序。但是,如果一個外部變量在多個源文件中定義卻沒有指定初始值,那么**某些系統(tǒng)會接受這個程序,而另外一些系統(tǒng)則不會接受。**所以,每個外部變量必須只定義一次。
3 命名沖突
3.1 命名沖突
如果在兩個不同的源文件中都包括了定義
int a;
那么它要么表示程序錯誤(如果鏈接器進制外部變量重復(fù)命名的話),要么在兩個源文件中共享a的同一個實例(無論兩個源文件中的外部變量是否應(yīng)該被共享)。即使其中a的一個定義是出現(xiàn)在系統(tǒng)提供的庫文件中,也仍然進行同樣的處理。
3.2 static修飾符
static int a;
static修飾a之后,a的作用域?qū)⒈幌拗圃谝粋€源文件中,對于其它源文件,a是不可見的,且無法再被extern所引用,當(dāng)然,static也適用于函數(shù)。使用static之后,我們就可以在其它的源文件中定義和這個已經(jīng)被static修飾后的同名的變量或者函數(shù)。
4 形參、實參、返回值
如果我們使用的函數(shù)并未進行聲明,但是已經(jīng)在后面進行了定義,此時會默認(rèn)函數(shù)返回類型為int型,這會造成極其嚴(yán)重的后果。
使用的函數(shù)如果在使用之前并未定義或者可能在其他的文件中,那么就要進行聲明,函數(shù)聲明的目的就是告知編譯器函數(shù)的返回值的類型。
注意:如果一個函數(shù)沒有float、short、或者char類型的參數(shù),在函數(shù)聲明中完全可以省略掉參數(shù)類型的說明(注意,函數(shù)定義中不能省略參數(shù)類型的說明)。這種做法依賴于調(diào)用者能夠提供數(shù)目正確且類型恰當(dāng)?shù)膶崊?。這里,“恰當(dāng)”并不意味著“等同”:float類型的參數(shù)會自動轉(zhuǎn)換為double類型,short或者char類型的參數(shù)會自動轉(zhuǎn)換為int類型。
在ANSI C標(biāo)準(zhǔn)發(fā)布之前,常常會有下面的這種聲明和定義函數(shù)的方式:
int isvowel();//聲明函數(shù)的方式 int isvowel(c) char c; { return c =='a' ; }
實際上,上面這種寫法與下面這種寫法是等價的:
int isvowel(int i) { char c; return c=='a'; }
上述兩種方式在VS2019中都是支持的。
看下面的例子:
#include<stdio.h> int main() { int i; char c; for (i = 0; i < 5; i++) { scanf("%d", &c); printf("%d ", i); } printf("\n"); return 0; }
表面上,這個程序從標(biāo)準(zhǔn)輸入設(shè)備讀入5個數(shù),在標(biāo)準(zhǔn)輸出設(shè)備設(shè)備上寫5個數(shù):
0 1 2 3 4
實際上,這個程序并不一定得到上面的結(jié)果。例如,在某個編譯器上,它的輸出是(當(dāng)然,在VS2019環(huán)境下程序會崩潰,因為非法修改了內(nèi)存空間)
0 0 0 0 0 1 2 3 4
為什么呢?問題的關(guān)鍵在于,這里的c被聲明為char類型,而不是int類型。如果程序要求scanf讀入一個整數(shù),應(yīng)該傳遞給他一個指向整數(shù)的指針。而程序中scanf函數(shù)得到的卻是一個指向字符的指針,scanf函數(shù)并不能分辨這種情況,它只是將這個指向字符的指針作為指向整數(shù)的指針而接受,并且在指針指向的位置存儲一個整數(shù)。因為整數(shù)所占的存儲空間要大于字符所占的存儲空間,所以字符c附近的內(nèi)存被覆蓋。
字符c附近的內(nèi)存中存儲的內(nèi)容是由編譯器決定的,在本例中它所存放的是整數(shù)i的低端部分。因此,每次讀入一個數(shù)值到c時,都會將i的低端部分覆蓋為0,而i的高端部分本來就是0,相當(dāng)于i每次被重新設(shè)置為0,循環(huán)將一直進行。當(dāng)?shù)竭_(dá)文件的結(jié)束位置后,scanf函數(shù)不再試圖讀入新的值到c。這時,i才可以正常的運行,最后終止循環(huán)。
5 檢查外部類型
注意:保證一個特定類型的所有外部定義在每個目標(biāo)模塊中都有相同的類型,“相同的類型”也應(yīng)該是嚴(yán)格意義上的相同。
例如,在一個文件中包含定義:
char filename[] = "/etc/passwd";
而在另一個文件中包含聲明:
extern char *filename;
在定義時,filename是一個字符數(shù)組的名稱。盡管在一個語句中引用filename的值將得到指向該數(shù)組起始元素的指針,但是filename的類型是”字符數(shù)組“,而不是字符指針。在第二個聲明中,filename被確定為一個指針。這兩種方式使用存儲空間的方式是不同的,它們無法以一種合乎情理的方式共存。第一個例子字符數(shù)組filename的內(nèi)存布局如下圖所示:
第二種方式字符指針filename的內(nèi)存布局如下圖所示:
修改方法如下圖所示:
char filename[] = "/etc/passwd"; extern char filename[];
也可以這樣進行修改:
char*filename = "/etc/passwd"; extern char *filename;
6 頭文件
注意:每個外部對象只在一個地方聲明,這個聲明的地方一般就在頭文件種,需要用到該外部對象的所有模塊也應(yīng)該 包括在這個頭文件。特別指出的是,定義該外部對象的模塊也應(yīng)該包括這個頭文件。
到此這篇關(guān)于C語言?超詳細(xì)講解鏈接器的文章就介紹到這了,更多相關(guān)C語言 鏈接器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
結(jié)構(gòu)體類型數(shù)據(jù)作為函數(shù)參數(shù)(三種方法)
將一個結(jié)構(gòu)體中變量中的數(shù)據(jù)傳遞給另一個函數(shù),有以下三種方法。需要的朋友可以過來參考下,希望對大家有所幫助2013-10-10C++數(shù)據(jù)結(jié)構(gòu)之二叉搜索樹的實現(xiàn)詳解
二叉搜索樹作為一個經(jīng)典的數(shù)據(jù)結(jié)構(gòu),具有鏈表的快速插入與刪除的特點,同時查詢效率也很優(yōu)秀,所以應(yīng)用十分廣泛。本文將詳細(xì)講講二叉搜索樹的C++實現(xiàn),需要的可以參考一下2022-08-08C語言實現(xiàn)統(tǒng)計一行字符串的單詞個數(shù)
這篇文章主要介紹了C語言實現(xiàn)統(tǒng)計一行字符串的單詞個數(shù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07Java C++ 算法題解leetcode1582二進制矩陣特殊位置
這篇文章主要為大家介紹了Java C++ 算法題解leetcode1582二進制矩陣特殊位置示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09