亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > C > 正文

C語言中的鏈接編寫教程

2020-01-26 14:59:22
字體:
來源:轉載
供稿:網友

鏈接
  鏈接就是將不同部分的代碼和數據收集和組合成為一個單一文件的過程,這個文件可被加載或拷貝到存儲器執行.
  鏈接可以執行與編譯時(源代碼被翻譯成機器代碼時),也可以執行與加載時(在程序被加載器加載到存儲器并執行時),甚至執行與運行時,由應用程序來執行.在現代系統中,鏈接是由鏈接器自動執行的.
  鏈接器分為:靜態鏈接器和動態鏈接器兩種.
靜態鏈接器
  靜態鏈接器以一組可重定位目標文件和命令行參數作為輸入,生成一個完全鏈接的可以加載和運行的可執行目標文件作為輸出.

  靜態鏈接器主要完成兩個任務:
  1>符號解析:目標文件定義和引用符號.符號解析的目的在于將每個符號引用和一個符號定義聯系起來.
  2>重定位:編譯器和匯編器生成從地址零開始的代碼和數據節.鏈接器通過把每個符號定義和一個存儲器位置聯系起來,然后修改所有對這些符號的引用,使得他們執行這個存儲位置,從而重定位這些節.

  目標文件:
  目標文件有三種形式:
  1>可重定位的目標文件:
  包含二進制代碼和數據,其形式可以再編譯時與其他可定位目標文件合并起來,創建一個可執行目標文件.
  2>可執行目標文件:
  包含二進制代碼和數據,其形式可以被直接拷貝到存儲器并執行.
  3>共享目標文件:
  一種特殊的可重定位目標文件,可以再加載或運行時,被動態地夾在到存儲器并執行.
  編譯器和匯編器生成可重定位目標文件(包括共享目標文件),鏈接器生成可執行目標文件.

  可重定位目標文件:
  EF頭L以一個16字節的序列開始,這個序列描述了字的大小和生成該文件的系統字節順序.ELF頭剩下的部分包含幫助鏈接器解析和解釋目標文件的信息.其中包括ELF頭的大小,目標文件的類型(比如,可重定位,可執行,共享目標文件),機器類型,節頭部表的文件偏移,以及節頭部表中的表目大小和數量.不同節的位置和大小是節頭部表描述的,其中目標文件中的每個節都有一個固定大小的表目.ELF格式的可重定位目標文件結構如下圖:

2015810110408527.jpg (278×310)

.text:已編譯程序的機器代碼
.rodata:只讀數據
.data:已初始化的全局C變量
.bss:未初始化的全局C變量.在目標文件中這個節不占實際空間,僅是一個占位符.
.sysmtab:一個符號表,存放在程序中被定義和引用的函數和全局變量的信息.
.rel.text:當鏈接器把這個目標文件和其他文件結合時,.text節中的許多位置都需要修改.一般而言,任何調用外部函數或者引用全局變量的指令都要修改.另一個方面,調用本地函數的指令則不需要修改.
.rel.data:被模塊定義或引用的任何全局變量的信息.
.debug:一個調試符號表
.line:原始C源程序中的行號和.text節中機器指令之間的映射.
.strtab:一個字符串表,其中內容包括.symtab和.debug節中的符號表,以及節頭部中的節名字.

 

  符號和符號表
  每個可重定位目標模塊m都有一個符號表,它包含m所定義和引用的符號的信息.在鏈接器上下文中,有三種不同的符號:
  1>由m定義并能被其他模塊引用的全局符號.全局鏈接器符號對應于非靜態的C函數以及被定義為不帶C的static屬性的全局變量.
  2>由其他模塊定義并被模塊m引用的全局符號.這些符號成為外部符號,對應于定義在其他模塊中的C函數和變量.
  3>只被模塊m定義和引用的本地符號.有的本地符號鏈接器符號對應于帶static屬性的C函數和全局變量.這些符號在模塊m中的任何地方都可見,但是不能被其他模塊引用.目標文件中對應于模塊m的節和相應的源文件的名字也能獲得本地符號.

  符號表式有匯編器構造的,使用編譯器輸出到匯編語言.s文件中的符號.sysmab節中包含ELF符號表.這張符號表包含一個關于表目的數組.表目的格式如下:

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;

符號解析
  鏈接器解析符號引用的方法是將每個引用和它輸入的可重定位目標文件按的符號表中的一個確定的符號定義聯系起來.
  對于那些和引用定義在相同模塊的本地符號的引用,符號解析式非常簡單明了的.編譯器只允許每個模塊中的每個本地符號只有一個定義.編譯器還確保靜態本地變量,它們會有本地鏈接器符號,擁有唯一的名字.
  對于全局符號的引用解析,當編譯器遇到一個不是在當前模塊中定義的符號(變量或函數名)時,它會假設該符號式在其他某個模塊中定義的,生成一個鏈接器符號表表目,并把它交給鏈接器處理.如果鏈接器在它的任何輸入模塊中都找不到這個被引用的符號,它就輸出一條錯誤信息并終止.
  在編譯時,編譯器輸出的每個全局符號給匯編器,或者是強,或者是弱,而匯編器把這個信息隱含地編碼在可重定位目標文件的符號表中.函數和以初始化的全局變量是強符號,未初始化的全局變量是弱符號.
  根據符號的強弱,有如下規則:
  1>不允許有多個強符號
  2>如果有一個強符號和多個弱符號,則選擇強符號
  3>如果有多個弱符號,則任選一個弱符號

  與靜態庫鏈接
  所有編譯系統都提供一種機制,將所有相關的目標模塊打包為一個單獨的文件,稱為靜態庫,它可以用做鏈接器的輸入.當鏈接器構造一個輸出的可執行文件時,它只拷貝靜態庫里被應用程序引用的目標模塊.
  在unix系統中,靜態庫以一種稱為存檔的特殊文件格式存放在磁盤中.存檔文件是一組連接起來的可重定位目標文件的集合,有一個頭部描述每個成員目標文件的大小和位置.

  鏈接器如何使用靜態庫來解析引用
  在符號解析階段,鏈接器從左到右按照它們在編譯驅動程序命令行上出現的相同順序來掃描可重定位目標文件和存檔文件.在這次掃描中,鏈接器位置一個可重定位目標文件集合E,這個集合中的文件會被合并起來形成可執行文件,和一個未解析的符號集合U,以及一個在前面輸入文件中已定義的符號結合D.初始時,E,U,D都是空的.
  1>對于命令行上的每個輸入文件f,鏈接器會判斷f是一個目標文件還是一個存檔文件.如果是一個目標文件,那么鏈接器把f添加到E,修改U和D來反映f中的符號定義和引用,并繼續下一個輸入文件.
  2>如果f是一個存檔文件,那么鏈接器就嘗試匹配U中未解析的符號由存檔文件成員定義的符號.如果某個存檔文件成員m,定義了一個符號來解析U中的一個引用,那么就將m加到E中,并且鏈接器修改U和D來反映m中的符號定義和引用.對存檔文件中的所有成員目標文件都反復進行這個過程,知道U和D都不再發生變化.在此時,任何不包含在E中的成員目標文件都會被丟棄,而鏈接器將繼續到下一個輸入文件.
  3>如果當鏈接器完成對輸入命令行的掃描后,U是非空的,那么鏈接器就會輸出一個錯誤并終止.否則,它會合并重定位E中的目標文件,從而構建輸出的可執行文件.

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

重定位
  當鏈接器完成了符號解析這一步時,它就把代碼中的每個符號引用和確定的一個符號定義(也就是,它的一個輸入目標模塊中的一個符號表表目)聯系起來.此時,鏈接器就知道它的輸入目標模塊中的代碼節和數據解的確切大小.然后就開始重定位步驟.重定位由兩步組成:
  1>重定位節和符號定義:
  在這一步中,鏈接器將所有相同類型的節合并為一個新的聚合節.然后,鏈接器將運行時存儲器地址賦值給新的聚合節,賦給輸入模塊定義的每個節,以及賦給輸入模塊定義的每個符號.當這一步完成時,程序中的每個指令和全局變量都一個唯一的運行時存儲器地址.
  2>重定位節中的符號引用:
  在這一步中,鏈接器修改代碼節和數據節中對每個符號的引用,使得它們指向正確的運行時地址.為了執行這一步,鏈接器依賴于稱為重定位表目的可重定位目標模塊中的數據結構.

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

動態鏈接器
  共享庫是一個目標模塊,在運行時,可以加載到任意的存儲器地址,并在存儲器中和一個程序鏈接起來.這個過程稱為動態鏈接,是由動態鏈接器完成的.
  共享庫的共享在兩個方面有所不同.首先,在任何給定的文件系統中,對于一個庫只有一個.so文件.所有引用該庫德可執行目標文件共享這個.so文件中的代碼和數據,而不是像靜態庫德內容那樣被拷貝和嵌入到引用它們的可執行的文件中.其次,在存儲器中,一個共享庫的.text節只有一個副本可以被不同的正在運行的進程共享.

  多目標文件的鏈接
stack.c

   

#include <stdio.h>      #define STACKSIZE 1000      typedef struct stack {     int data[STACKSIZE];     int top;   } stack;      stack s;   int count = 0;      void pushStack(int d)   {     s.data[s.top ++] = d;     count ++;   }      int popStack()   {     return s.data[-- s.top];   }      int isEmpty()   {     return s.top == 0;   } 


link.c

 

  #include <stdio.h>      int a, b;      int main()   {     a = b = 1;        pushStack(a);     pushStack(b);     pushStack(a);        while (! isEmpty()) {       printf("%d/n", popStack());     }          return 0;   } 


編譯方式:

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

提示出錯信息如下:

2015810110529983.png (828×104)

但是代碼是可以執行的

定義和聲明

static和extern修飾函數
上述編譯出現錯誤的原因是:編譯器在處理函數調用代碼時沒有找到函數原型,只好根據函數調用代碼做隱式聲明,把這三個函數聲明為:

  int pushStack(int);   int popStack(void);   int isEmpty(void); 


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

 

  #include <stdio.h>      int 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("%d/n", popStack());     }          return 0;   } 


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

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

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


編譯鏈接在一起會出錯,原因是:

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

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


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

  

 #include <stdio.h>      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("%d/n", count);        while (! isEmpty()) {       printf("%d/n", popStack());     }          return 0;   } 


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

如果不想在stack.c外讓外界訪問到count,則可以用static關鍵字將count聲明為Internal Linkage
區別
變量生命和函數聲明有一點不同,函數聲明的extern可寫可不寫,而變量聲明如果不寫extern,意思就完全變了。如果上面的例子不寫extern就表示在main函數中定義一個全局變量count。

用static關鍵字聲明具有Internal Linkage的函數和關鍵字是處于保護內部狀態的目的,也是一種封裝(Encapsulation)的思想。一個模塊中,有些函數是提供給外界使用的,也稱為導出(Export)給外界使用,這些函數用extern聲明為External Linkage的。


頭文件
為了防止每次函數extern聲明,例如又有一個foo.c也使用pushStack等函數,又需要在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里就只需要包含這個頭文件就可以了,而不需要寫三個函數聲明了:

   

 #include <stdio.h>   #include "stack.h"      int a, b;      extern int count;      int main()   {     a = b = 1;        pushStack(a);     pushStack(b);     pushStack(a);        printf("%d/n", count);        while (! isEmpty()) {       printf("%d/n", popStack());     }          return 0;   } 


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

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


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

  •     使預處理的速度變慢了,要處理很多本來不需要處理的頭文件
  •     如果a.h包含了b.h,然后b.h又包含了a.h的情況,預處理就陷入死循環了
  •     頭文件按有些代碼不允許重復出現


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

 

 

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表

圖片精選

亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美性猛交视频| 久久久久女教师免费一区| 中文字幕无线精品亚洲乱码一区| 国产欧美在线播放| 欧美在线日韩在线| 国产欧美va欧美va香蕉在线| 久久99国产精品久久久久久久久| 国产精品入口免费视频一| 黑人巨大精品欧美一区免费视频| 26uuu另类亚洲欧美日本老年| 欧美一区三区三区高中清蜜桃| 精品视频久久久久久久| 国产亚洲一区精品| 中文在线资源观看视频网站免费不卡| 亚洲欧美激情精品一区二区| 97久久久免费福利网址| 国产精品久久久久aaaa九色| 欧美激情中文字幕乱码免费| 国产欧美日韩中文字幕| www.亚洲男人天堂| 欧美在线视频a| 91高清免费视频| 亚洲欧洲日本专区| 午夜精品久久久99热福利| 成人福利视频在线观看| 欧美精品一本久久男人的天堂| 91精品国产自产在线| 国内精品久久久久影院 日本资源| 在线观看欧美视频| 成人做爽爽免费视频| 欧美精品videos性欧美| 日韩av电影在线免费播放| 国产一区二区三区视频在线观看| 97视频在线观看播放| 欧美精品videosex极品1| 一区二区三区精品99久久| 最近2019年手机中文字幕| 色999日韩欧美国产| 久久久久久国产精品久久| 色播久久人人爽人人爽人人片视av| 日韩欧美成人免费视频| 97久久久免费福利网址| 国产精品一区二区av影院萌芽| 日韩欧美中文免费| 亚洲精品99久久久久中文字幕| 国产伦精品一区二区三区精品视频| 亚洲精品自在久久| 国产精品久久婷婷六月丁香| 亚洲免费高清视频| 欧美激情综合亚洲一二区| 欧美日韩精品二区| 91国内精品久久| 国产精品久久久久不卡| 日本高清不卡的在线| 高清视频欧美一级| 国产精品国产三级国产aⅴ浪潮| 久久久久久亚洲精品不卡| 久久久av免费| 久久影院中文字幕| 欧美精品午夜视频| 久久久免费在线观看| 91免费看片在线| 精品国产一区二区三区久久狼黑人| 国产精品久久久久久av福利软件| 日韩欧美在线字幕| 青青久久av北条麻妃海外网| 中文字幕亚洲激情| 精品久久久一区二区| 亚洲天堂男人天堂| 欧美电影免费观看电视剧大全| 97国产精品视频人人做人人爱| 久久九九精品99国产精品| 中文字幕亚洲色图| 亚洲色图欧美制服丝袜另类第一页| 国产午夜精品一区理论片飘花| 色无极影院亚洲| 国产女精品视频网站免费| 亚洲精品一区二区在线| 国产中文字幕日韩| 最近2019中文免费高清视频观看www99| 成人国产精品久久久| 成人午夜在线观看| 姬川优奈aav一区二区| 久久久之久亚州精品露出| 欧美精品在线播放| 欧美日韩一区二区在线| 中文字幕精品久久久久| 亚洲欧洲日产国码av系列天堂| 欧美日韩午夜视频在线观看| 日韩av在线免费观看一区| 国产精品极品在线| 久久精品视频中文字幕| 久久久亚洲精选| 日韩中文有码在线视频| 亚洲美女视频网站| 国产91精品久久久久| 欧美精品在线播放| 国产综合香蕉五月婷在线| 狠狠色香婷婷久久亚洲精品| 国产91热爆ts人妖在线| 久久国产精品久久久久久久久久| 日韩中文字幕国产精品| 国产精品免费观看在线| 亚洲国产精品网站| 国产成人av在线播放| 欧美亚洲视频在线看网址| 久久影视电视剧免费网站| 欧美丝袜一区二区| 亚洲黄色av女优在线观看| 亚洲一区二区三区乱码aⅴ蜜桃女| 91国内免费在线视频| 精品视频9999| 亚洲成人精品久久久| 欧美性猛交xxxx黑人猛交| 亚洲男人7777| 欧美激情视频一区| 伊人久久五月天| 国产精品三级在线| 国产精品999| 亚洲欧洲在线免费| 亚洲成人免费网站| 日韩美女主播视频| 久久在线视频在线| 狠狠爱在线视频一区| 在线电影av不卡网址| 国产欧美婷婷中文| 国产精品一区二区三区免费视频| 亚洲自拍偷拍第一页| 国产69久久精品成人| 国产精品福利网站| 国产免费亚洲高清| 亚洲福利精品在线| 欧美—级a级欧美特级ar全黄| 成人黄色免费在线观看| 国产精品美女久久久久久免费| 一本大道久久加勒比香蕉| 欧美中文在线观看国产| 国产精品一区二区女厕厕| www.日韩视频| 911国产网站尤物在线观看| 国产aⅴ夜夜欢一区二区三区| 国产精品久久久久久久久免费| 2019中文字幕免费视频| 日韩av中文字幕在线免费观看| 91av在线免费观看| 国产不卡视频在线| 国外色69视频在线观看| 久久久之久亚州精品露出| 国产成人精品在线| 日韩欧美一区视频| 国产亚洲精品激情久久| 亚洲成人激情视频| 国产精品视频成人| 日韩中文字幕在线视频播放| 丁香五六月婷婷久久激情| 日韩视频免费看| 欧美激情欧美激情| 91精品啪在线观看麻豆免费| 成人福利网站在线观看| 在线观看91久久久久久| 亚洲色图第三页| 日韩大片在线观看视频| 日韩av电影免费观看高清| 韩国国内大量揄拍精品视频|