1. 程式人生 > >C++編譯與連結

C++編譯與連結

做地址重定向表address redirect table。
      對於1.o來說,它的重定向表為
      地址
      0x005
      這個表不需要符號,當連結器處理這個表的時候,發現地址為0x005的位置上有一個地址需要重定向,那麼直接在以0x005開始的4個位元組上加上0x2000就可以了。
讓我們總結一下:編譯器把一個cpp編譯為目標檔案的時候,除了要在目標檔案裡寫入cpp裡包含的資料和程式碼,還要至少提供3個表:未解決符號表,匯出符號表和地址重定向表。
未解決符號表提供了所有在該編譯單元裡引用但是定義並不在本編譯單元裡的符號及其出現的地址。
      匯出符號表提供了本編譯單元具有定義,並且願意提供給其他編譯單元使用的符號及其地址。
      地址重定向表提供了本編譯單元所有對自身地址的引用的記錄。
      連結器進行連結的時候,首先決定各個目標檔案在最終可執行檔案裡的位置。然後訪問所有目標檔案的地址重定向表,對其中記錄的地址進行重定向(即加上該編譯單元實際在可執行檔案裡的起始地址)。然後
遍歷所有目標檔案的未解決符號表,並且在所有的匯出符號表裡查詢匹配的符號,並在未解決符號表中所記錄的位置上填寫實際的地址(也要加上擁有該符號定義的編譯單元實際在可執行檔案裡的起始地址)。最後把所有的目標檔案的內容寫在各自的位置上,再作一些別的工作,一個可執行檔案就出爐了。
      最終link 1.o 2.o .... 所生成的可執行檔案大概是
      0x00000000  ????(別的一些資訊)
      ....
      0x00001000  inc DWORD PTR [0x00002000]              //這裡是2.o的開始,也就是g的定義
      0x00001005  ret                                  //假設inc為5個位元組,這裡是g的結尾

      ....
      0x00002000  0x00000001                           //這裡是1.o的開始,也是n的定義(初始化為1)
      0x00002004  inc DWORD PTR [0x00002000]         //這裡是f的開始
      0x00002009  ret                                  //假設inc為5個位元組,這裡是f的結尾
      ...
      ...
      實際連結的時候更為複雜,因為實際的目標檔案裡把資料/程式碼分為好幾個區,重定向等要按區進行,但原理是一樣的。

       現在我們可以來看看幾個經典的連結錯誤了:
       unresolved external link..

       這個很顯然,是連結器發現一個未解決符號,但是在匯出符號表裡沒有找到對應的項。
       解決方案麼,當然就是在某個編譯單元裡提供這個符號的定義就行了。(注意,這個符號可以是一個變數,也可以是一個函式),也可以看看是不是有什麼該連結的檔案沒有連結
       duplicated external simbols...
這個則是匯出符號表裡出現了重複項,因此連結器無法確定應該使用哪一個。這可能是使用了重複的名稱,也可能有別的原因。

       我們再來看看C/C++語言裡針對這一些而提供的特性:
       extern:這是告訴編譯器,這個符號在別的編譯單元裡定義,也就是要把這個符號放到未解決符號表裡去。(外部連結)
       static:如果該關鍵字位於全域性函式或者變數的宣告的前面,表明該編譯單元不匯出這個函式/變數的符號。因此無法在別的編譯單元裡使用。(內部連結)。如果是static區域性變數,則該變數的儲存方式和全域性變數一樣,但是仍然不匯出符號。
       預設連結屬性:對於函式和變數,模認外部連結,對於const變數,預設內部連結。(可以通過新增extern和static改變連結屬性)
外部連結的利弊:外部連結的符號,可以在整個程式範圍內使用(因為匯出了符號)。但是同時要求其他的編譯單元不能匯出相同的符號(不然就是duplicated external simbols)
       內部連結的利弊:內部連結的符號,不能在別的編譯單元內使用。但是不同的編譯單元可以擁有同樣名稱的內部連結符號。

為什麼標頭檔案裡一般只可以有宣告不能有定義