日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

C/C++?-?從代碼到可執行程序的過程詳解_C 語言

作者:give?it?a?try~ ? 更新時間: 2023-03-11 編程語言

(1)預編譯

主要處理源代碼文件中的以“#”開頭的預編譯指令。處理規則見下:

刪除所有的#define,展開所有的宏定義。處理所有的條件預編譯指令,如“#if”、“#endif”、“#ifdef”、“#elif”和“#else”。處理“#include”預編譯指令,將文件內容替換到它的位置,這個過程是遞歸進行的,文件中包含其
他文件。刪除所有的注釋,“//”和“/**/”。保留所有的#pragma 編譯器指令,編譯器需要用到他們,如:#pragma once 是為了防止有文件
被重復引用。添加行號和文件標識,便于編譯時編譯器產生調試用的行號信息,和編譯時產生編譯錯誤或警告是
能夠顯示行號。

(2)編譯

把預編譯之后生成的xxx.i或xxx.ii文件,進行一系列詞法分析、語法分析、語義分析及優化后,生成相應
的匯編代碼文件。

詞法分析:利用類似于“有限狀態機”的算法,將源代碼程序輸入到掃描機中,將其中的字符序列分
割成一系列的記號。語法分析:語法分析器對由掃描器產生的記號,進行語法分析,產生語法樹。由語法分析器輸出的
語法樹是一種以表達式為節點的樹。語義分析:語法分析器只是完成了對表達式語法層面的分析,語義分析器則對表達式是否有意義進
行判斷,其分析的語義是靜態語義——在編譯期能分期的語義,相對應的動態語義是在運行期才能
確定的語義。優化:源代碼級別的一個優化過程。目標代碼生成:由代碼生成器將中間代碼轉換成目標機器代碼,生成一系列的代碼序列——匯編語
言表示。目標代碼優化:目標代碼優化器對上述的目標機器代碼進行優化:尋找合適的尋址方式、使用位移
來替代乘法運算、刪除多余的指令等。

(3)匯編

將匯編代碼轉變成機器可以執行的指令(機器碼文件)。 匯編器的匯編過程相對于編譯器來說更簡單,沒
有復雜的語法,也沒有語義,更不需要做指令優化,只是根據匯編指令和機器指令的對照表一一翻譯過
來,匯編過程有匯編器as完成。經匯編之后,產生目標文件(與可執行文件格式幾乎一樣)xxx.o(Windows 下)、xxx.obj(Linux下)。

(4)鏈接

將不同的源文件產生的目標文件進行鏈接,從而形成一個可以執行的程序。鏈接分為靜態鏈接和動態鏈
接:

靜態鏈接

函數和數據被編譯進一個二進制文件。在使用靜態庫的情況下,在編譯鏈接可執行文件時,鏈接器從庫
中復制這些函數和數據并把它們和應用程序的其它模塊組合起來創建最終的可執行文件。

以下面這個圖來簡單說明一下從靜態鏈接到可執行文件的過程,根據在源文件中包含的頭文件和程序中使用到的庫函數,如stdio.h中定義的printf()函數,在libc.a中找到目標文件printf.o(這里暫且不考慮printf()函數的依賴關系),然后將這個目標文件和我們hello.o這個文件進行鏈接形成我們的可執行文件。

在這里插入圖片描述

這里有一個小問題,就是從上面的圖中可以看到靜態運行庫里面的一個目標文件只包含一個函數,如libc.a里面的printf.o只有printf()函數,strlen.o里面只有strlen()函數。

我們知道,鏈接器在鏈接靜態鏈接庫的時候是以目標文件為單位的。比如我們引用了靜態庫中的printf()函數,那么鏈接器就會把庫中包含printf()函數的那個目標文件鏈接進來,如果很多函數都放在一個目標文件中,很可能很多沒用的函數都被一起鏈接進了輸出結果中。由于運行庫有成百上千個函數,數量非常龐大,每個函數獨立地放在一個目標文件中可以盡量減少空間的浪費,那些沒有被用到的目標文件就不要鏈接到最終的輸出文件中。

缺點:
空間浪費:因為每個可執行程序中對所有需要的目標文件都要有一份副本,所以如果多個程序對同一個
目標文件都有依賴,會出現同一個目標文件都在內存存在多個副本;
更新困難:每當庫函數的代碼修改了,這個時候就需要重新進行編譯鏈接形成可執行程序。
運行速度快:但是靜態鏈接的優點就是,在可執行程序中已經具備了所有執行程序所需要的任何東西,
在執行的時候運行速度快。

動態鏈接

動態鏈接的基本思想是把程序按照模塊拆分成各個相對獨立部分,在程序運行時才將它們鏈接在一起形成一個完整的程序,而不是像靜態鏈接一樣把所有程序模塊都鏈接成一個單獨的可執行文件。

假設現在有兩個程序program1.o和program2.o,這兩者共用同一個庫lib.o,假設首先運行程序program1,系統首先加載program1.o,當系統發現program1.o中用到了lib.o,即program1.o依賴于lib.o,那么系統接著加載lib.o,如果program1.o和lib.o還依賴于其他目標文件,則依次全部加載到內存中。當program2運行時,同樣的加載program2.o,然后發現program2.o依賴于lib.o,但是此時lib.o已經存在于內存中,這個時候就不再進行重新加載,而是將內存中已經存在的lib.o映射到program2的虛擬地址空間中,從而進行鏈接(這個鏈接過程和靜態鏈接類似)形成可執行程序。

優點:
共享庫:就是即使需要每個程序都依賴同一個庫,但是該庫不會像靜態鏈接那樣在內存中存在多分,副
本,而是這多個程序在執行時共享同一份副本;更新方便:更新時只需要替換原來的目標文件,而無需將所有的程序再重新鏈接一遍。當程序下一次運行時,新版本的目標文件會被自動加載到內存并且鏈接起來,程序就完成了升級的目標。
性能損耗:因為把鏈接推遲到了程序運行時,所以每次執行程序都需要進行鏈接,所以性能會有一定損失。

生成可執行文件

什么情況會編譯成功但鏈接失敗

(1)說明并使用了類型、函數、變量,但沒給出相應類型、函數或變量的定義。關于說明和定義的區別見上述教程,說明可以通過#include頭文件進行,也可以直接進行說明如int f( )或extern int f( )。若同時給出函數f的函數體則稱為定義。對于變量若有初始值就算定義,例如"extern int x=3; "便是定義,這種語法有其獨特應用背景。

(2)對標準庫的連接失敗,例如調用了sin(double x),卻找不到數學運算標準庫進行連接了,可能是安裝后因各種可能原因被刪除了。

(3)全局變量和靜態變量存放在數據段,而固定大小的數據段不夠用了,例如定義太多的全局變量和靜態變量,甚至巨型數組全局或靜態變量。

(4)數據段被偷用后無法容納較多的全局變量和靜態變量,這種錯誤最難發現且最難改正。主要是一些常量在偷用數據段,例如字符串常量"abc"等等,其它諸多情形參見上述教程。另外虛函數入口地址表等也會偷用。

原文鏈接:https://blog.csdn.net/moasad/article/details/128666033

欄目分類
最近更新