最新c語言的連接符號大全

格式:DOC 上傳日期:2023-04-22 21:12:42
最新c語言的連接符號大全
時間:2023-04-22 21:12:42     小編:zdfb

人的記憶力會隨著歲月的流逝而衰退,寫作可以彌補記憶的不足,將曾經(jīng)的人生經(jīng)歷和感悟記錄下來,也便于保存一份美好的回憶。大家想知道怎么樣才能寫一篇比較優(yōu)質(zhì)的范文嗎?下面是小編幫大家整理的優(yōu)質(zhì)范文,僅供參考,大家一起來看看吧。

c語言的連接符號篇一

如果你有一個c++文件,而你只有一些相關類的聲明, 那么你如何用c調(diào)用呢?c語言中的鏈接編寫再怎么寫呢?歡迎大家閱讀!更多相關信息請關注相關欄目!

鏈接就是將不同部分的代碼和數(shù)據(jù)收集和組合成為一個單一文件的過程,這個文件可被加載或拷貝到存儲器執(zhí)行.

鏈接可以執(zhí)行與編譯時(源代碼被翻譯成機器代碼時),也可以執(zhí)行與加載時(在程序被加載器加載到存儲器并執(zhí)行時),甚至執(zhí)行與運行時,由應用程序來執(zhí)行.在現(xiàn)代系統(tǒng)中,鏈接是由鏈接器自動執(zhí)行的.

鏈接器分為:靜態(tài)鏈接器和動態(tài)鏈接器兩種.

靜態(tài)鏈接器以一組可重定位目標文件和命令行參數(shù)作為輸入,生成一個完全鏈接的可以加載和運行的可執(zhí)行目標文件作為輸出.

靜態(tài)鏈接器主要完成兩個任務:

1>符號解析:目標文件定義和引用符號.符號解析的目的在于將每個符號引用和一個符號定義聯(lián)系起來.

2>重定位:編譯器和匯編器生成從地址零開始的代碼和數(shù)據(jù)節(jié).鏈接器通過把每個符號定義和一個存儲器位置聯(lián)系起來,然后修改所有對這些符號的引用,使得他們執(zhí)行這個存儲位置,從而重定位這些節(jié).

目標文件有三種形式:

1>可重定位的目標文件:

包含二進制代碼和數(shù)據(jù),其形式可以再編譯時與其他可定位目標文件合并起來,創(chuàng)建一個可執(zhí)行目標文件.

2>可執(zhí)行目標文件:

包含二進制代碼和數(shù)據(jù),其形式可以被直接拷貝到存儲器并執(zhí)行.

3>共享目標文件:

一種特殊的可重定位目標文件,可以再加載或運行時,被動態(tài)地夾在到存儲器并執(zhí)行.

編譯器和匯編器生成可重定位目標文件(包括共享目標文件),鏈接器生成可執(zhí)行目標文件.

ef頭l以一個16字節(jié)的序列開始,頭剩下的部分包含幫助鏈接器解析和解釋目標文件的信息.其中包括elf頭的大小,目標文件的類型(比如,可重定位,可執(zhí)行,共享目標文件),機器類型,節(jié)頭部表的文件偏移,以及節(jié)頭部表中的表目大小和數(shù)量.不同節(jié)的位置和大小是節(jié)頭部表描述的,格式的可重定位目標文件結構如下圖:

.text:已編譯程序的機器代碼

.rodata:只讀數(shù)據(jù)

.data:已初始化的全局c變量

.bss:未初始化的全局c變量.在目標文件中這個節(jié)不占實際空間,僅是一個占位符.

.sysmtab:一個符號表,存放在程序中被定義和引用的函數(shù)和全局變量的信息.

.:當鏈接器把這個目標文件和其他文件結合時,.text節(jié)中的許多位置都需要修改.一般而言,任何調(diào)用外部函數(shù)或者引用全局變量的指令都要修改.另一個方面,調(diào)用本地函數(shù)的指令則不需要修改.

.:被模塊定義或引用的任何全局變量的信息.

.debug:一個調(diào)試符號表

.line:節(jié)中機器指令之間的映射.

.strtab:一個字符串表,節(jié)中的符號表,以及節(jié)頭部中的節(jié)名字.

每個可重定位目標模塊m都有一個符號表,它包含m所定義和引用的符號的信息.在鏈接器上下文中,有三種不同的符號:

1>由m定義并能被其他模塊引用的全局符號.全局鏈接器符號對應于非靜態(tài)的c函數(shù)以及被定義為不帶c的static屬性的全局變量.

2>由其他模塊定義并被模塊m引用的全局符號.這些符號成為外部符號,對應于定義在其他模塊中的c函數(shù)和變量.

3>只被模塊m定義和引用的本地符號.有的本地符號鏈接器符號對應于帶static屬性的c函數(shù)和全局變量.這些符號在模塊m中的任何地方都可見,但是不能被其他模塊引用.目標文件中對應于模塊m的節(jié)和相應的源文件的名字也能獲得本地符號.

符號表式有匯編器構造的,節(jié)中包含elf符號表.這張符號表包含一個關于表目的數(shù)組.表目的格式如下:

typedef struct{ int name; //string table offset int value; //section offset, or vm address int size; //object size in bytes char type:4, //data, func, section, or src file binding:4; //local or global char reserved; //unused char section; //section header index, abs, undef, or common}elf_symbol;

鏈接器解析符號引用的方法是將每個引用和它輸入的可重定位目標文件按的符號表中的一個確定的符號定義聯(lián)系起來.

對于那些和引用定義在相同模塊的本地符號的引用,符號解析式非常簡單明了的.編譯器只允許每個模塊中的每個本地符號只有一個定義.編譯器還確保靜態(tài)本地變量,它們會有本地鏈接器符號,擁有唯一的名字.

對于全局符號的引用解析,當編譯器遇到一個不是在當前模塊中定義的符號(變量或函數(shù)名)時,它會假設該符號式在其他某個模塊中定義的,生成一個鏈接器符號表表目,并把它交給鏈接器處理.如果鏈接器在它的任何輸入模塊中都找不到這個被引用的符號,它就輸出一條錯誤信息并終止.

在編譯時,編譯器輸出的每個全局符號給匯編器,或者是強,或者是弱,而匯編器把這個信息隱含地編碼在可重定位目標文件的符號表中.函數(shù)和以初始化的全局變量是強符號,未初始化的全局變量是弱符號.

根據(jù)符號的強弱,有如下規(guī)則:

1>不允許有多個強符號

2>如果有一個強符號和多個弱符號,則選擇強符號

3>如果有多個弱符號,則任選一個弱符號

所有編譯系統(tǒng)都提供一種機制,將所有相關的目標模塊打包為一個單獨的文件,稱為靜態(tài)庫,它可以用做鏈接器的輸入.當鏈接器構造一個輸出的可執(zhí)行文件時,它只拷貝靜態(tài)庫里被應用程序引用的目標模塊.

在unix系統(tǒng)中,靜態(tài)庫以一種稱為存檔的特殊文件格式存放在磁盤中.存檔文件是一組連接起來的可重定位目標文件的集合,有一個頭部描述每個成員目標文件的大小和位置.

在符號解析階段,鏈接器從左到右按照它們在編譯驅(qū)動程序命令行上出現(xiàn)的相同順序來掃描可重定位目標文件和存檔文件.在這次掃描中,鏈接器位置一個可重定位目標文件集合e,這個集合中的文件會被合并起來形成可執(zhí)行文件,和一個未解析的符號集合u,以及一個在前面輸入文件中已定義的符號結合d.初始時,e,u,d都是空的.

1>對于命令行上的每個輸入文件f,鏈接器會判斷f是一個目標文件還是一個存檔文件.如果是一個目標文件,那么鏈接器把f添加到e,修改u和d來反映f中的符號定義和引用,并繼續(xù)下一個輸入文件.

2>如果f是一個存檔文件,那么鏈接器就嘗試匹配u中未解析的符號由存檔文件成員定義的符號.如果某個存檔文件成員m,定義了一個符號來解析u中的一個引用,那么就將m加到e中,并且鏈接器修改u和d來反映m中的符號定義和引用.對存檔文件中的所有成員目標文件都反復進行這個過程,知道u和d都不再發(fā)生變化.在此時,任何不包含在e中的成員目標文件都會被丟棄,而鏈接器將繼續(xù)到下一個輸入文件.

3>如果當鏈接器完成對輸入命令行的掃描后,u是非空的,那么鏈接器就會輸出一個錯誤并終止.否則,它會合并重定位e中的目標文件,從而構建輸出的可執(zhí)行文件.

這種方式,導致了在輸入命令時要考慮到,靜態(tài)庫和目標文件的位置,庫文件放在目標文件的后面,如果庫文件之間有引用關系,則被引用的庫放在后面.

當鏈接器完成了符號解析這一步時,它就把代碼中的每個符號引用和確定的一個符號定義(也就是,它的一個輸入目標模塊中的一個符號表表目)聯(lián)系起來.此時,鏈接器就知道它的輸入目標模塊中的代碼節(jié)和數(shù)據(jù)解的確切大小.然后就開始重定位步驟.重定位由兩步組成:

在這一步中,鏈接器將所有相同類型的節(jié)合并為一個新的聚合節(jié).然后,鏈接器將運行時存儲器地址賦值給新的聚合節(jié),賦給輸入模塊定義的每個節(jié),以及賦給輸入模塊定義的每個符號.當這一步完成時,程序中的每個指令和全局變量都一個唯一的運行時存儲器地址.

在這一步中,鏈接器修改代碼節(jié)和數(shù)據(jù)節(jié)中對每個符號的引用,使得它們指向正確的運行時地址.為了執(zhí)行這一步,鏈接器依賴于稱為重定位表目的'可重定位目標模塊中的數(shù)據(jù)結構.

當匯編器生成一個目標模塊時,它并不知道數(shù)據(jù)和代碼最終將存放在存儲器中的什么位置.它也不知道這個模塊引用的任何外部定義的函數(shù)或者全局變量的位置.所以,無論何時匯編器遇到對最終位置未知的目標引用,它就會生成一個重定位表目,告訴鏈接器在將目標文件合并為可執(zhí)行文件時,如何修改這個引用.代碼的重定位表目放在.中.已初始化數(shù)據(jù)的重定位表目放在中.

elf重定位表目的格式如下:

typedef struct{

int offset; //offset of the reference to relocate

int symbol:24, //symbol the reference point to

type:8; //relocation type

} elf32_rel;

elf定義了11中不同的重定位類型,其中最基本的兩種重定位類型是:r_386_pc32(重定位一個使用32pc相關的地址引用)和r_386_32(重定位一個使用32位絕對地址的引用).

共享庫是一個目標模塊,在運行時,可以加載到任意的存儲器地址,并在存儲器中和一個程序鏈接起來.這個過程稱為動態(tài)鏈接,是由動態(tài)鏈接器完成的.

共享庫的共享在兩個方面有所不同.首先,在任何給定的文件系統(tǒng)中,文件中的代碼和數(shù)據(jù),而不是像靜態(tài)庫德內(nèi)容那樣被拷貝和嵌入到引用它們的可執(zhí)行的文件中.其次,在存儲器中,節(jié)只有一個副本可以被不同的正在運行的進程共享.

stack.c

#include#define stacksize 1000

typedef struct stack {

int data[stacksize];

int top;

} stack;

stack s;

int count = 0;

void pushstack(int d)

{

[ ++] = d;

count ++; } int popstack()

{

return [-- ];

}

int isempty()

{

return == 0;

}

link.c

#includeint a, b;

int main()

{

a = b = 1;

pushstack(a);

pushstack(b);

pushstack(a);

while (!isempty()) {

printf("%dn", popstack());

}

return 0;

}

編譯方式:

gcc -wall stack.c link.c -o main

提示出錯信息如下:

但是代碼是可以執(zhí)行的

上述編譯出現(xiàn)錯誤的原因是:編譯器在處理函數(shù)調(diào)用代碼時沒有找到函數(shù)原型,只好根據(jù)函數(shù)調(diào)用代碼做隱式聲明,把這三個函數(shù)聲明為:

int pushstack(int); int popstack(void); int isempty(void);

編譯器往往不知道去哪里找函數(shù)定義,像上面的例子,我讓編譯器編譯main.c,而這幾個函數(shù)定義卻在stack.c里,編譯器無法知道,因此可以用extern聲明。修改link.c如下:

#includeint a, b; extern void pushstack(int d); extern int popstack(void); extern int isempty(void); int main() { a = b = 1; pushstack(a); pushstack(b); pushstack(a); while (! isempty()) { printf("%dn", popstack()); } return 0; }

這樣編譯器就不會報警了。這里extern關鍵字表示這個標識符具有external ack這個標識符具有external linkage指的是:如果link.c和stack.c鏈接在一起,如果pushstack在link.c和stack.c中都聲明(在stack.c中的聲明同時也是定義),那么這些聲明指的是同一個函數(shù),鏈接后是同一個global符號,代表同一個地址。函數(shù)聲明中的extern可以省略不寫,不屑extern的函數(shù)聲明也表示這個函數(shù)具有external linkage。

如果用static關鍵字修飾一個函數(shù)聲明,則表示該標識符具有internal linkage,例如有以下兩個程序文件:

/* foo.c */ static void foo(void) {} /*main.c*/ void foo(void); int main(void) { foo(); return 0;}

雖然在foo.c中定義了函數(shù)foo,但是這個函數(shù)是static屬性,只具有internal linkage。如果把foo.c編譯成目標文件,函數(shù)名foo在其中是一個local的符號,不參與鏈接過程,所以在鏈接時,main.c中用到一個external linkage的foo函數(shù),鏈接器卻找不到它的定義在哪,無法確定它的地址,也就無法做符號解析,只好報錯。

凡是被多次聲明的變量或函數(shù),必須有且只有一個聲明是定義,如果有多個定義,或者一個定義都沒有,鏈接器就無法完成鏈接

如果我想在link.c中訪問stack.c中定義的int變量count,則可以用extern聲明

#include

int a, b;

extern void pushstack(int d);

extern int popstack(void);

extern int isempty(void);

extern int count;

int main()

{

a = b = 1;

pushstack(a);

pushstack(b);

pushstack(a);

printf("%dn", count);

while (! isempty()) {

printf("%dn", popstack());

}

return 0;

}

變量count具有external linkage,它的存儲空間是在stack.c中分配的,所以link.c中的變量聲明extern int count;不是變量定義,因為它不分配存儲空間。

如果不想在stack.c外讓外界訪問到count,則可以用static關鍵字將count聲明為internal linkage

變量生命和函數(shù)聲明有一點不同,函數(shù)聲明的extern可寫可不寫,而變量聲明如果不寫extern,意思就完全變了。如果上面的例子不寫extern就表示在main函數(shù)中定義一個全局變量count。

用static關鍵字聲明具有internal linkage的函數(shù)和關鍵字是處于保護內(nèi)部狀態(tài)的目的,也是一種封裝(encapsulation)的思想。一個模塊中,有些函數(shù)是提供給外界使用的,也稱為導出(export)給外界使用,這些函數(shù)用extern聲明為external linkage的。

為了防止每次函數(shù)extern聲明,例如又有一個foo.c也使用pushstack等函數(shù),又需要在foo.c中寫多個extern聲明,為了避免這種重復麻煩的操作,可以自己定義一個stack.h頭文件:

#ifndef stack_h

#define stack_h

#define stacksize 1000

typedef struct stack {

int data[stacksize];

int top;

} stack;

extern void pushstack(int d);

extern int popstack(void);

extern int isempty(void);

#endif

這樣,在link.c里就只需要包含這個頭文件就可以了,而不需要寫三個函數(shù)聲明了:

#include

#include "stack.h"

int a, b;

extern int count;

int main() {

a = b = 1;

pushstack(a);

pushstack(b);

pushstack(a);

printf("%dn", count);

while (! isempty()) {

printf("%dn", popstack());

}

return 0;

}

為什么#include用角括號,而#include "stack.h"用引號?原因:

對于用角括號包含的頭文件,gcc首先查找-i選項指定的目錄,然后查找系統(tǒng)的頭文件目錄(通常是/usr/include) 對于用“”包含的頭文件,gcc首先查找包含頭文件的.c文件所在的目錄,然后查找-i選項指定的目錄,然后查找系統(tǒng)的頭文件目錄

用#ifndef #define #endif是為了防止頭文件的重復包含,頭文件重復包含的問題如下:

使預處理的速度變慢了,要處理很多本來不需要處理的頭文件 如果a.h包含了b.h,然后b.h又包含了a.h的情況,預處理就陷入死循環(huán)了

頭文件中的變量和函數(shù)聲明一定不能是定義。如果頭文件中出現(xiàn)變量或函數(shù)定義,這個頭文件又被多個.c文件包含,那么這些.c文件就不能鏈接在一起。

s("content_relate");

【c語言中的鏈接編寫】相關文章:

1.c語言鏈接程序

2.c語言中的運算規(guī)則

3.c語言中的字符(char)

4.c語言中的assert用法

5.c語言中default的用法

6.c語言中free的用法

7. c語言中的指針解讀

8.c語言中bit的用法

【本文地址:http://mlvmservice.com/zuowen/2676548.html】

全文閱讀已結束,如果需要下載本文請點擊

下載此文檔