走進C 語言:你知道C語言程序是如何執行的嗎?


C 語言程序成為高級語言的原因是它能夠讀取並理解人們的思想。然而,為了能夠在系統中運行 hello.c 程序,則各個 C 語句必須由其他程序轉換為一系列低級機器語言指令。這些指令被打包作為可執行對象程序,存儲在二進制磁盤文件中。目標程序也稱為可執行目標文件。

在 UNIX 系統中,從源文件到對象文件的轉換是由編譯器執行完成的。

gcc -o hello hello.c

gcc 編譯器驅動從源文件讀取 hello.c ,並把它翻譯成一個可執行文件 hello。這個翻譯過程可用如下圖來表示


 

這就是一個完整的 hello world 程序執行過程,會涉及幾個核心組件:預處理器、編譯器、匯編器、連接器,下面我們逐個擊破。

預處理階段(Preprocessing phase),預處理器會根據開始的 # 字符,修改源 C 程序。#include <stdio.h>命令就會告訴預處理器去讀系統頭文件 stdio.h 中的內容,並把它插入到程序作為文本。然后就得到了另外一個 C 程序hello.i,這個程序通常是以 .i為結尾。

然后是 編譯階段(Compilation phase),編譯器會把文本文件 hello.i 翻譯成文本hello.s,它包括一段匯編語言程序(assembly-language program)。

編譯完成之后是匯編階段(Assembly phase),這一步,匯編器 as會把 hello.s 翻譯成機器指令,把這些指令打包成可重定位的二進制程序(relocatable object program)放在 hello.c 文件中。它包含的 17 個字節是函數 main 的指令編碼,如果我們在文本編輯器中打開 hello.o 將會看到一堆亂碼。

最后一個是鏈接階段(Linking phase),我們的 hello 程序會調用 printf 函數,它是 C 編譯器提供的 C 標准庫中的一部分。printf 函數位於一個叫做 printf.o文件中,它是一個單獨的預編譯好的目標文件,而這個文件必須要和我們的 hello.o 進行鏈接,連接器(ld) 會處理這個合並操作。結果是,hello 文件,它是一個可執行的目標文件(或稱為可執行文件),已准備好加載到內存中並由系統執行。


 

你需要理解編譯系統做了什么

對於上面這種簡單的 hello 程序來說,我們可以依賴編譯系統(compilation system)來提供一個正確和有效的機器代碼。然而,對於我們上面講的程序員來說,編譯器有幾大特征你需要知道

優化程序性能(Optimizing program performance),現代編譯器是一種高效的用來生成良好代碼的工具。對於程序員來說,你無需為了編寫高質量的代碼而去理解編譯器內部做了什么工作。然而,為了編寫出高效的 C 語言程序,我們需要了解一些基本的機器碼以及編譯器將不同的 C 語句轉化為機器代碼的過程。

理解鏈接時出現的錯誤(Understanding link-time errors),在我們的經驗中,一些非常復雜的錯誤大多是由鏈接階段引起的,特別是當你想要構建大型軟件項目時。

避免安全漏洞(Avoiding security holes),近些年來,緩沖區溢出(buffer overflow vulnerabilities)是造成網絡和 Internet 服務的罪魁禍首,所以我們有必要去規避這種問題。

系統硬件組成

為了理解 hello 程序在運行時發生了什么,我們需要首先對系統的硬件有一個認識。下面這是一張 Intel 系統產品的模型,我們來對其進行解釋


 

總線(Buses):在整個系統中運行的是稱為總線的電氣管道的集合,這些總線在組件之間來回傳輸字節信息。通常總線被設計成傳送定長的字節塊,也就是 字(word)。字中的字節數(字長)是一個基本的系統參數,各個系統中都不盡相同。現在大部分的字都是 4 個字節(32 位)或者 8 個字節(64 位)。


 

I/O 設備(I/O Devices):Input/Output 設備是系統和外部世界的連接。上圖中有四類 I/O 設備:用於用戶輸入的鍵盤和鼠標,用於用戶輸出的顯示器,一個磁盤驅動用來長時間的保存數據和程序。剛開始的時候,可執行程序就保存在磁盤上。 每個I/O 設備連接 I/O 總線都被稱為控制器(controller) 或者是 適配器(Adapter)。控制器和適配器之間的主要區別在於封裝方式。控制器是 I/O 設備本身或者系統的主印制板電路(通常稱作主板)上的芯片組。而適配器則是一塊插在主板插槽上的卡。無論組織形式如何,它們的最終目的都是彼此交換信息。


 

主存(Main Memory),主存是一個臨時存儲設備,而不是永久性存儲,磁盤是 永久性存儲 的設備。主存既保存程序,又保存處理器執行流程所處理的數據。從物理組成上說,主存是由一系列 DRAM(dynamic random access memory) 動態隨機存儲構成的集合。邏輯上說,內存就是一個線性的字節數組,有它唯一的地址編號,從 0 開始。一般來說,組成程序的每條機器指令都由不同數量的字節構成,C 程序變量相對應的數據項的大小根據類型進行變化。比如,在 Linux 的 x86-64 機器上,short 類型的數據需要 2 個字節,int 和 float 需要 4 個字節,而 long 和 double 需要 8 個字節。

處理器(Processor),CPU(central processing unit) 或者簡單的處理器,是解釋(並執行)存儲在主存儲器中的指令的引擎。處理器的核心大小為一個字的存儲設備(或寄存器),稱為程序計數器(PC)。在任何時刻,PC 都指向主存中的某條機器語言指令(即含有該條指令的地址)。 從系統通電開始,直到系統斷電,處理器一直在不斷地執行程序計數器指向的指令,再更新程序計數器,使其指向下一條指令。處理器根據其指令集體系結構定義的指令模型進行操作。在這個模型中,指令按照嚴格的順序執行,執行一條指令涉及執行一系列的步驟。處理器從程序計數器指向的內存中讀取指令,解釋指令中的位,執行該指令指示的一些簡單操作,然后更新程序計數器以指向下一條指令。指令與指令之間可能連續,可能不連續(比如 jmp 指令就不會順序讀取) 下面是 CPU 可能執行簡單操作的幾個步驟

加載(Load):從主存中拷貝一個字節或者一個字到內存中,覆蓋寄存器先前的內容

存儲(Store):將寄存器中的字節或字復制到主存儲器中的某個位置,從而覆蓋該位置的先前內容

操作(Operate):把兩個寄存器的內容復制到 ALU(Arithmetic logic unit) 。把兩個字進行算術運算,並把結果存儲在寄存器中,重寫寄存器先前的內容。

算術邏輯單元(ALU)是對數字二進制數執行算術和按位運算的組合數字電子電路。

跳轉(jump):從指令中抽取一個字,把這個字復制到程序計數器(PC) 中,覆蓋原來的值


 

剖析 hello 程序的執行過程

前面我們簡單的介紹了一下計算機的硬件的組成和操作,現在我們正式介紹運行示例程序時發生了什么,我們會從宏觀的角度進行描述,不會涉及到所有的技術細節。

剛開始時,shell 程序執行它的指令,等待用戶鍵入一個命令。當我們在鍵盤上輸入了 ./hello 這幾個字符時,shell 程序將字符逐一讀入寄存器,再把它放到內存中,如下圖所示


 

當我們在鍵盤上敲擊回車鍵的時候,shell 程序就知道我們已經結束了命令的輸入。然后 shell 執行一系列指令來加載可執行的 hello 文件,這些指令將目標文件中的代碼和數據從磁盤復制到主存。

利用 DMA(Direct Memory Access) 技術可以直接將磁盤中的數據復制到內存中,如下


 

一旦目標文件中 hello 中的代碼和數據被加載到主存,處理器就開始執行 hello 程序的 main 程序中的機器語言指令。這些指令將 hello,world\n 字符串中的字節從主存復制到寄存器文件,再從寄存器中復制到顯示設備,最終顯示在屏幕上。如下所示


 

高速緩存是關鍵

上面我們介紹完了一個 hello 程序的執行過程,系統花費了大量時間把信息從一個地方搬運到另外一個地方。hello 程序的機器指令最初存儲在磁盤上。當程序加載后,它們會拷貝到主存中。當 CPU 開始運行時,指令又從內存復制到 CPU 中。同樣的,字符串數據 hello,world \n 最初也是在磁盤上,它被復制到內存中,然后再到顯示器設備輸出。從程序員的角度來看,這種復制大部分是開銷,這減慢了程序的工作效率。因此,對於系統設計來說,最主要的一個工作是讓程序運行的越來越快。

由於物理定律,較大的存儲設備要比較小的存儲設備慢。而由於寄存器和內存的處理效率在越來越大,所以針對這種差異,系統設計者采用了更小更快的存儲設備,稱為高速緩存存儲器(cache memory, 簡稱為 cache 高速緩存),作為暫時的集結區域,存放近期可能會需要的信息。如下圖所示


 

圖中我們標出了高速緩存的位置,位於高速緩存中的 L1高速緩存容量可以達到數萬字節,訪問速度幾乎和訪問寄存器文件一樣快。容量更大的 L2 高速緩存通過一條特殊的總線鏈接 CPU,雖然 L2 緩存比 L1 緩存慢 5 倍,但是仍比內存要更快 5 - 10 倍。L1 和 L2 是使用一種靜態隨機訪問存儲器(SRAM) 的硬件技術實現的。最新的、處理器更強大的系統甚至有三級緩存:L1、L2 和 L3。系統可以獲得一個很大的存儲器,同時訪問速度也更快,原因是利用了高速緩存的 局部性原理。


 

最后,如果你也想成為程序員,想要快速掌握編程,趕緊加入學習企鵝圈子!

里面有資深專業軟件開發工程師,在線解答你的所有疑惑~編程語言入門“so easy”

編程學習書籍:


 

編程學習視頻:


 


免責聲明!

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



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