中文字幕理论片,69视频免费在线观看,亚洲成人app,国产1级毛片,刘涛最大尺度戏视频,欧美亚洲美女视频,2021韩国美女仙女屋vip视频

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
內功修煉:程序是如何運行起來的

對于任何一個學習過C語言的來說,“HelloWorld”程序都不會陌生。因為它應該是你打開新世界的看到的第一束光。至今我還記得第一次敲出這個程序的時候激動了好久。但是你們知道短短的幾行代碼,是怎么讓程序運行起來的么?

// hello.c#include <stdio.h>int main(int argc, char *argv[]) {    printf('Hello World!\n');    return 0;}

程序是如何運行起來的?很多人可能會說,不就是五個步驟:預處理(Prepressing),編譯(Compilation),匯編(Assembly)鏈接(Linking),裝載(Loading)么?是這樣的。但是你知道每一步背后都做過一些什么嗎?如果你能回答上以下的問題,我想這個文章就沒有必要看下去了。

  • 在main()函數調用之前,程序做過一些什么?

  • 編譯出來的可執(zhí)行文件里面有什么,在內存中是什么樣子的,是怎么來組織的?

  • 靜態(tài)鏈接、動態(tài)鏈接,有什么區(qū)別?

  • 不同的編譯器(Micrsoft VC/VS, GCC)和不同的硬件平臺(X86,SPARC,MIPS,ARM),以及不同的操作系統(tǒng)(Windows,Linux,Unix,Solaris),最終編譯出來的結果一樣么?

  • ELF文件,PE文件,COFF文件,是什么?

如果你發(fā)現對其中的一些問題,不是很了解的話,甚至沒有想過這些問題的時候,而你有向了解一下,那么就可以,跟著我的步伐一步倆步,往下看啦。這個文章是為你準備的。需要聲明的是,本文主要針對gcc編譯器,也就是針對C和C ,不一定適用于其他語言的編譯。下圖為總覽。


GCC編譯過程

預處理

預處理的過程,其實,主要是處理那些源代碼中以#開始的預編譯指令。比如#include#define等,處理的規(guī)則如下:

  • 將所有的#define刪除,并且展開所有的宏定義

  • 處理所有的條件預編譯指令,比如#if#ifdef, #elif, #else, #endif

  • 處理#include預編譯指令,將被包含的文件插入到該預編譯指令的位置。在這個插入的過程中,是遞歸進行的,也就是說被包含的文件,可能還包含其他文件。

  • 刪除所有注釋 ///**/.

  • 添加行號和文件標識,以便編譯時產生調試用的行號及編譯錯誤警告行號。

  • 保留所有的#pragma編譯器指令,因為編譯器需要使用它們。

對于第一步預編譯的過程,可以通過以下方式完成:

gcc -E hello.c -o hello.i

或者

cpp hello.c > hello.i

編譯

編譯過程可分為6步:詞法分析、語法分析、語義分析、源代碼優(yōu)化、代碼生成、目標代碼優(yōu)化。對應與下圖的每一步。下面我們以一個具體的表達式進行分析:

array[index] = (index 4)*(2 6);


Compilation

  • 詞法分析:掃描器(Scanner)將源代的字符序列分割成一系列的記號(Token)。

記號類型
array標記符
[左方括號
index標記符
]右標記符
=賦值
(左圓括號
index標記符
加號
4數字
)右圓括號
*乘號
(左圓括號
2數字
加號
6數字
)右圓括號

注:lex工具,可實現按照用戶描述的詞法規(guī)則將輸入的字符串分割為一個一個記號。

  • 語法分析:語法分析器將記號(Token)產生語法樹(Syntax Tree)。


    Syntax Tree


     注:yacc工具(yacc: Yet Another Compiler Compiler)可實現語法分析,根據用戶給定的語法規(guī)則對輸入的記號序列進行解析,從而構建一個語法樹,所以它也被稱為“編譯器編譯器(Compiler Compiler)”。

  • 語義分析:編譯器所分析的語義是靜態(tài)語義,所謂靜態(tài)語義就是指在編譯期可以確定的語義,通常包括聲明,和類型的匹配,類型的轉換。


    Commented Syntax Tree


     注:與之對于的為動態(tài)語義分析,只有在運行期才能確定的語義。

  • 源代碼優(yōu)化:源代碼優(yōu)化器(Source Code Optimizer),在源碼級別進行優(yōu)化,例如(2  6)這個表達式,其值在編譯期就可以確定。優(yōu)化后的語法樹。


    Paste_Image.png


     但是直接作用于語法樹比較困難,所以源代碼優(yōu)化器往往將整個語法數轉化為中間代碼(Intermediate Code)。

    注:中間代碼是與目標機器和運行環(huán)境無關的。中間代碼使得編譯器被分為前端和后端。編譯器前端(1-4步)負責產生機器無關的中間代碼;編譯器后端(5-6步)將中間代碼轉化為目標機器代碼。

  • 目標代碼生成:代碼生成器(Code Generator)。

  • 目標代碼優(yōu)化:目標代碼優(yōu)化器(Target Code Optimizer)。

最后的倆個步驟十分依賴與目標機器,因為不同的機器有不同的字長,寄存器,整數數據類型和浮點數據類型等。

匯編

匯編器是將匯編代碼轉變成機器可以執(zhí)行的命令,每一個匯編語句幾乎都對應一條機器指令。匯編相對于編譯過程比較簡單,所以根據匯編指令和機器指令的對照表一一翻譯即可。匯編過程可以通過以下方式完成。

as hello.s -o hello.o

或者

gcc -c hello.s -o hello.o

鏈接

靜態(tài)鏈接

把一個程序分割為多個模塊,然后通過某種方式組合形成一個單一的程序,這就是鏈接。而模塊間如何組合的問題,歸根到底,就是模塊如何進行通信的倆個問題:(1) 模塊間的函數調用,(2) 模塊間的變量訪問。但無論是那一個問題,其本質是獲取一個地址,函數運行的地址、或者變量存放的地址。

如果熟悉匯編的,應該會知道hello.o文件,既目標文件,是以分段的形式組織在一起的。其簡單來說,把程序運行的地址劃分為了一段一段的片段,有的片段是用來存放代碼,叫代碼段,這樣,可以給這個段加個只讀的權限,防止程序被修改;有的片段用來存放數據,叫數據段,數據經常修改,所以可讀寫;有的片段用來存放標識符的名字,比如某個變量 ,某個函數,叫符號表;等等。由于有這么多段,所以為了方便管理,所以又引入了一個段,叫段表,方便查找每個段的位置。

當文件之間相互需要鏈接的時候,就把相同的段合并,然后把函數,變量地址修改到正確的地址上 。這就是靜態(tài)鏈接,如下圖。


靜態(tài)鏈接

但是這里有倆個問題:

  • 對于計算機的內存和磁盤的空間浪費比較嚴重

    想想一下,現在一個靜態(tài)庫,至少都是1MB以上。但是假如有1000個或者更多的程序在鏈接的時候,都靜態(tài)鏈接了它,那么當這些程序運行起來的時候,內存中就會存在1000 相同的副本,還是一模一樣的。這樣,至少1GB空間就浪費了。

  • 程序的更新,部署,和發(fā)布會帶來很多麻煩

    比如一個程序Program所使用的Lib.o是使用的第三方廠商提供的,那么當該廠商更新了Lib.o(比如修復了一個bug,或者優(yōu)化了性能),那么Program的廠商就必須要拿到最新版的Lib.o,然后與Program.o鏈接。將新的Program發(fā)給用戶。這樣,一旦程序任何位置有一個小小的改動,都會導致重新下載整個程序。

動態(tài)鏈接

我們的想法很簡單,就是當第一個例子在運行時,在內存中只有一個副本;第二個例子在發(fā)生時,只需要下載更新后的lib,然后鏈接,就好了。那么其實,這就是動態(tài)鏈接的基本思想了:把鏈接這個過程推遲到運行的時候在進行。在運行的時候動態(tài)的選擇加載各種程序模塊,這個優(yōu)點,就是后來被人們用來制作程序的插件(Plug-in)。

這里,我們不得不介紹一個東西,叫做動態(tài)鏈接器。它會在程序運行的時候,把程序中所有未定義的符號(比如調了動態(tài)庫的一個函數,或者訪問了一個變量)綁定到動態(tài)鏈接庫中。簡單的來說就是把程序中函數的地址改正到動態(tài)庫,之后動態(tài)鏈接器會把控制權交給程序,然后程序執(zhí)行。

這種在裝載時修正地址,經常被稱為裝載時重定位(Load Time Relocation)。而靜態(tài)鏈接時修正,則被稱為鏈接時重定位(Link Time Relocation)。

可能有的人,就要問了,多個程序應用一個庫不會有問題么?變量沖突?是這樣的。動態(tài)鏈接文件,把那些需要修改的部分分離了出來,與數據放在了一起,這樣指令部分就可以保持不變,而數據部分可以在每個進程中擁有一個副本,這種方案就是目前被稱為地址無關代碼(PIC,Position-independent Code)的技術。

鏈接庫

通過上面,我們了解到了動態(tài)鏈接,靜態(tài)鏈接。一組相應目標文件的集合,我們稱它為庫。因而也就有了靜態(tài)鏈接庫,動態(tài)鏈接庫。

  • 靜態(tài)鏈接庫:在Linux平臺上,常以.a或者.o為拓展名的文件,我們最常用的C語言靜態(tài)庫,就位于/usr/lib/libc.a;而在Windows平臺上,常以.lib為拓展名的文件,比如Visual C 附帶的多個版本C/C 運行庫,在VC安裝的目錄下的`lib`目錄。

  • 動態(tài)鏈接庫:在Linux平臺上,動態(tài)鏈接文件為稱為動態(tài)共享對象(DSO,Dynamic Shared Objects),簡稱共享對象。他們一般常以.so為拓展名的文件;而在Windows平臺上,動態(tài)鏈接文件被稱為動態(tài)鏈接庫(DLL,Dynamical Linking Library),通常就是我們常見的.dll為拓展名的文件。

裝載

介紹裝載就不得不介紹三種文件格式了:ELF,PE,COFF?,F在PC平臺上流行的可執(zhí)行文件格式(Executable),無論是Windows下的PE(Portable Executable)文件,還是Linux下的ELF(Executable Linkable Format)文件,都是COFF(Common file format)文件格式的變種。可執(zhí)行文件例如,Windows下的*.exe,Linux下的/bin/bash。其實目標文件,內部結構上來說和可執(zhí)行文件的結構幾乎是一樣的,所以一般跟可執(zhí)行文件格式一起用一種格式進行存儲。

下面以ELF文件為例子,介紹。

每一個ELF文件,都會有一個ELF文件頭,里面會記錄很多關于這個程序相關信息,通過它確定段表,進而確定各個段。總的來說,裝載做了以下三件事情:

  • 創(chuàng)建虛擬地址空間

  • 讀取可執(zhí)行文件頭,并且建立虛擬空間與可執(zhí)行文件的映射關系

  • 將CPU的指令寄存器設置為運行庫的初始函數(初始函數不止一個,第一個啟動函數為:_start),初始了main()函數的環(huán)境,然后指向可執(zhí)行文件的入口


以上就是最近幾天看完《程序員的自我修養(yǎng)》一些感悟吧。
└(^o^)┘;



本文轉自:簡書

微信號:IdeaofSE


本站僅提供存儲服務,所有內容均由用戶發(fā)布,如發(fā)現有害或侵權內容,請點擊舉報
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
C/C++程序編譯流程詳解
編譯匯編原理
GCC常用參數
再理解編譯鏈接過程(GCC編譯器)
編譯和鏈接那點事<上> | 淺墨的部落格
gcc/g++編譯器的安裝與說明
更多類似文章 >>
生活服務
熱點新聞
分享 收藏 導長圖 關注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點擊這里聯系客服!

聯系客服