c++編譯過程簡介


  • 了解編譯過程的益處
    • c++工程相關的問題
      • 什么是庫?靜態庫和動態庫又有什么區別?
      • 頭文件起什么作用?
  • 編譯過程簡介
    • 名詞:
      • 編譯:把源文件中的源代碼翻譯成機器語言,保存到目標文件中。如果編譯通過,就會把CPP轉換成OBJ文件。
      • 編譯單元:
        • 每個cpp就是一個編譯單元,每個編譯單元相互之間是獨立且相互不知的。一個編譯單元(Translation Unit)是指一個.cpp文件以及這所include的所有.h文件,.h文件里面的代碼將會被擴展到包含它的.cpp文件里,然后編譯器編譯該.cpp文件為一個.obj文件,后者擁有PE(Portable Executable,即Windows可執行文件)文件格式,並且本身包含的就是二進制代碼,但是不一定能執行,因為並不能保證其中一定有main函數。當編譯器將一個工程里的所有.cpp文件以分離的方式編譯完畢后,再由鏈接器進行鏈接成為一個.exe或.dll文件。
      • 目標文件:編譯后生成的文件,以機器碼的形式包含了編譯單元里所有的函數和數據、導出符號表、未解決符號表、地址重定向表
        • 目標文件的類型:
          • 可重定位文件(.o、.obj文件):其中包含有適合於其它目標文件鏈接來創建一個可執行的或者共享的目標文件的代碼和數據。每個cpp會被編譯成一個.o文件
          • 共享的目標文件(庫文件)
            • 這種文件存放了適合於在兩種上下文里鏈接的代碼和數據。
              • 第一種是鏈接程序(靜態庫)可把它與其它可重定位文件及共享的目標文件一起處理來創建另一個目標文件
                • 靜態鏈接庫實際上是一個目標文件的集合,其中的每個文件含有庫中的一個或者一組相關函數的代碼
              • 第二種是動態鏈接程序(動態庫)將它與另一個可執行文件及其它的共享目標文件結合到一起,創建一個進程映象
                • 動態鏈接庫在程序執行時才被調用
          • 可執行文件
            • 一個可以被操作系統創建一個進程來執行之的文件
        • .o文件在編譯后就能獲得,但是庫文件、可執行文件都需要在鏈接后才能獲得
    • c++程序編譯過程圖
      •  
    • 編譯過程
      • 作用:編譯是讀取源程序(字符流),對之進行詞法和語法的分析,將高級語言指令轉換為功能等效的匯編代碼,再轉換為機器代碼,生成目標文件(.obj)
      • 分為兩個過程
        • 編譯: 
          • 預處理階段
            • 宏#define
            • 條件編譯指令,如#ifdef,#ifndef,#else,#elif,#endif等。
            • 頭文件包含,#include <iostream>
            • 特殊符號
              • LINE標識將被解釋為當前行號(十進制
                數)
              • FILE則被解釋為當前被編譯的C源程序的名稱。預編譯程序對 於在源程序中出現的這些串將用合適的值進行替換
          • 編譯、優化階段
            • 針對代碼優化,不依賴具體計算機
            • 針對計算機優化
        • 匯編
          • 把匯編語言代碼翻譯成目標機器指令,生成目標文件(.o文件、.obj文件)。此過程會依賴機器的硬件和操作系統環境。
      • 3張表:.o文件至少要提供3張表
        • 導出符號表:即該目標文件可以提供的符號及地址
        • 未解決符號表:即找不到地址的符號的列表,告訴鏈接器這些符號沒找到地址
        • 地址重定向表:
          • 鏈接的時候,鏈接器會為目標文件的“未解決符號表”里的符號在其他目標文件中尋找地址,但是每個目標文件的地址都是從0x0000開始的,這樣直接將對方文件中符號的地址拿過來用顯然會是不正確的,為了區分不同的文件,鏈接器在鏈接時就會對每個目標文件的地址進行調整。在這個例子中,假如B.obj的0x0000被定位到可執行文件的0x00001000上,而A.obj的0x0000被定位到可執行文件的0x00002000上,那么實現上對鏈接器來說,A.obj的導出符號地地址都會加上0x00002000,B.obj所有的符號地址也會加上0x00001000。這樣就可以保證地址不會重復。
          • 因為被加上了起始地址,所以符號在自身文件中的實際地址就不對了,需要再用一張地址重定向表記錄符號相對自身文件的地址
      • 例子:
    • 鏈接過程
      • 鏈接:鏈接程序的主要工作就是將有關的目標文件(庫文件、.o文件)彼此相連接,也即將在一個文件中引用的符號同該符號在另外一個文件中的定義連接起來,使得所有的這些目標文件成為一個能夠被操作系統裝入執行的統一整體。
      • 具體工作: 
        當鏈接器進行鏈接的時候,首先決定各個目標文件在最終可執行文件里的位置。然后訪問所有目標文件的地址重定義表,對其中記錄的地址進行重定向(加上一個偏移量,即該編譯單元在可執行文件上的起始地址)。然后遍歷所有目標文件的未解決符號表,並且在所有的導出符號表里查找匹配的符號,並在未解決符號表中所記錄的位置上填寫實現地址。最后把所有的目標文件的內容寫在各自的位置上,再作一些另的工作,就生成一個可執行文件。
      • 鏈接方式
        • 靜態鏈接:函數的代碼將從其所在地靜態鏈接庫中被拷貝到最終的可執行程序中。這樣該程序在被執行時這些代碼將被裝入到該進程的虛擬地址空間中。
        • 動態鏈接:函數的代碼被放到稱作是動態鏈接庫或共享對象的某個目標文件中。鏈接程序此時所作的只是在最終的可執行程序中
          記錄下共享對象的名字以及其它少量的登記信息。在此可執行文件被 執行時,動態鏈接庫的全部內容將被映射到運行時相應進程的虛地址 空間。動態鏈接程序將根據可執行程序中記錄的信息找到相應的函數
          代碼。
    • 兩種鏈接方式的比較
  • C/C++中提供的一些特性
    • extern:這就是告訴編譯器,這個變量或函數在別的編譯單元里定義了,也就是要把這個符號放到未解決符號表里面去(外部鏈接)。
    •  static:如果該關鍵字位於全局函數或者變量的聲明前面,表明該編譯單元不導出這個函數或變量,因些這個符號不能在別的編譯單元中使用(內部鏈接)。如果是static局部變量,則該變量的存儲方式和全局變量一樣,但是仍然不導出符號。
    • 默認鏈接屬性:對於函數和變量,默認鏈接是外部鏈接,對於const變量,默認內部鏈接。
    • 外部鏈接的利弊:外部鏈接的符號在整個程序范圍內都是可以使用的,這就要求其他編譯單元不能導出相同的符號(不然就會報 duplicated external symbols)。
    • 為什么頭文件里一般只可以有聲明不能有定義:頭文件可以被多個編譯單元包含,如果頭文件里面有定義的話,那么每個包含這頭文件的編譯單元都會對同一個符號進行定義,如果該符號為外部鏈接,則會導致duplicated external symbols鏈接錯誤。
    • 為什么公共使用的內聯函數要定義於頭文件里:因為編譯時編譯單元之間互不知道,如果內聯被定義於.cpp文件中,編譯其他使用該函數的編譯單元的時候沒有辦法找到函數的定義,因些無法對函數進行展開(內聯函數不展開,即不采用在使用處標記函數代碼再跳轉的方式,而是直接將代碼嵌入)。所以如果內聯函數定義於.cpp里,那么就只有這個.cpp文件能使用它。
    • .h中的inline 函數可以被多個cpp包含而不造成符號沖突,因為它會被直接嵌入到調用的地方,內部聯結不形成外部符號,對外不可見
  • 常見編譯器
  • makefile及make工具
  • 常見編譯器使用方法
  • 編譯錯誤解析





免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM