從表面上看,我是由Visual Studio建立的,而實際上,真正負責編譯源代碼建立生成可執行程序HelloWorld.exe的倒是Visual Studio中集成的C++編譯器cl.exe和連接器link.exe。他們二老,纔是個人親生爹媽。html
爲了便於人們的編寫、閱讀和維護,咱們的源文件是使用C++這種人們能夠理解的高級程序設計語言編寫的。然而,計算機卻並不理解這種高級語言,也就沒法直接執行高級語言編寫而成的源文件。因此,這裏就須要一個翻譯的工做,將源文件中人們能夠理解的C++高級語言翻譯成機器能夠理解執行的機器語言。我老爸編譯器其實是個翻譯官,他的工做就是將用C++這種高級語言編寫的源文件(.cpp)翻譯成用計算機能夠看懂的機器語言表示的目標文件(.obj),你們一般將這一過程稱爲編譯。ios
在Visual Studio中,我老爸的名字是cl.exe,你們能夠在開始菜單中找到「VS2012 開發人員命令提示」,而後在打開的DOS窗口中經過cl命令請他老人家出手,將一個cpp源文件編譯成相應的obj目標文件。好比,要想讓我爸將個人源文件HelloWorld.cpp編譯成對應的目標文件HelloWorld.obj,可使用下面的命令:程序員
cl /c /EHsc HelloWorld.cpp算法
其中,cl是調用編譯器的指令,其後的選項用於指定編譯器的編譯行爲。這裏的「/c」表示只編譯不連接;「/EHsc」指定編譯器使用何種異常處理模型;最後一個選項HelloWorld.cpp則是即將要編譯的C++源文件。源文件HelloWorld.cpp通過我爸編譯器的編譯後,獲得的還只是一個沒法直接執行的目標文件HelloWorld.obj,還須要我媽連接器將這個目標文件和Visual C++所提供的標準庫目標文件(好比,libcpmt.lib)整合成最終的可執行文件(從標準庫目標文件中查找程序目標文件所用到的外部函數等符號,而後填寫到程序目標文件以生成最終的可執行文件),這一過程就被稱之爲連接。在「VS2012 開發人員命令提示」中,你們能夠用以下的命令請我媽連接器link.exe來完成這一連接過程:數據結構
link HelloWorld.obj多線程
固然,整個編譯連接的工做,也能夠由我爸編譯器cl.exe一我的完成:函數
cl /EHsc HelloWorld.cpppost
通過我爸我媽的編譯連接過程,我從一個源文件(HelloWorld.cpp)變成了一個可執行文件(HelloWorld.exe),我就這樣哇哇墜地了。整個過程,如圖2-6所示:this
圖2-6 編譯連接過程url
一旦生成可執行文件,就能夠給操做系統下達指令讓文件開始執行。一個程序的執行是從其主函數開始的。可是在進入主函數開始執行以前,操做系統會幫咱們作不少準備工做。好比,當操做系統接到執行某個程序的指令後,它首先要建立相應的進程並分配私有的進程空間;而後加載器會把可執行文件的數據段和代碼段映射到進程的虛擬內存空間中;操做系統接着會初始化程序中定義的全局變量等。作好這些準備工做,程序就能夠進入主函數開始執行了。
進入主函數後,程序會按照源代碼給我制定的人生規劃,一條語句一條語句地往下執行,一步一步地往下走。你們必定還記得,個人源代碼是這樣的:
int main() { // 在屏幕上輸出「Hello World!」字符串 cout<<"Hello World!"<<endl; return 0; }
從這裏能夠看到,進入主函數後,個人第一條語句就是:
cout<<"Hello World!"<<endl;
這條語句的意思是讓我在DOS窗口中顯示「Hello World!」這樣一串文字,因而我便開始控制DOS窗口,在其中顯示這串文字,完成程序員經過這行代碼交給個人任務。
接下來的一條語句是:
return 0;
這條簡短的語句宣告了我人生歷程的結束。它表示主函數的結束,整個程序執行完畢。圖2-7所示的是我短暫而光輝的一輩子!
圖2-7 Hello World程序短暫而光輝的一輩子
知道更多:C++程序執行背後的故事
在上面的例子中,咱們看到一個C++程序的執行過程,是從main()函數開始逐條語句往下執行的。這個過程看起來很是簡單,但在每條語句的背後,都還有着更多的故事。
在Visual Studio調試模式下的反彙編視圖(在調試模式下經過Alt+8快捷鍵打開)中,咱們能夠看到C++程序中的各條語句所對應的彙編代碼。這下,程序中各條語句作了什麼事情、各個功能是如何實現的,都一目瞭然了。HelloWorld程序雖然只是簡單地輸出一個字符串,可是當咱們把這個程序拆解開,卻能夠發現它背後作了不少事情。在彙編視圖下的HelloWorld程序以下(彙編代碼太長,咱們只保留其中的關鍵操做):
#include <iostream>
using namespace std; int main() { // 完成準備工做 00DC4EC0 push ebp 00DC4EC1 mov ebp,esp 00DC4EC3 sub esp,0C0h // … 00DC4EDC rep stos dword ptr es:[edi] // 完成任務 // 在屏幕輸出「Hello World!」字符串 cout<<"Hello World!"<<endl; 00DC4EDE mov esi,esp 00DC4EE0 mov eax,dword ptr ds:[00DD031Ch] 00DC4EE5 push eax 00DC4EE6 push 0DCCC70h 00DC4EEB mov ecx,dword ptr ds:[0DD0318h] 00DC4EF1 push ecx // 調用標準庫中的操做符來完成任務 00DC4EF2 call std::operator<<<std::char_traits<char> > (0DC12A3h) 00DC4EF7 add esp,8 00DC4EFA mov ecx,eax 00DC4EFC call dword ptr ds:[0DD0324h] 00DC4F02 cmp esi,esp 00DC4F04 call __RTC_CheckEsp (0DC132Ah) return 0; 00DC4F09 xor eax,eax }
當咱們啓動一個程序後,操做系統會建立一個新的進程來執行這個程序。所謂進程,就是應用程序的一個實例。操做系統建立進程的時候,會爲其分配必定的內存空間(默認堆),做爲其私有的虛擬地址空間。一般,一個應用程序的執行對應於一個進程,進程負責管理這個程序運行時的一切事物,例如資源的分配與調度等等。可是,做爲程序執行的調度者,它並不負責程序的執行,具體的執行工做,則是由它所建立的線程來完成的。每一個進程都有一個主線程,若是是多線程應用程序,還能夠有多個輔助線程。線程並不擁有資源(它使用的是它所屬進程的資源),可是它擁有本身的執行入口、執行的順序系列和一個執行終點。
在這裏,當負責執行這個程序的主線程被建立之後,它就會進入main()函數開始執行。它首先會執行一些初始化工做,例如保存現場環境、對堆進行初始化以及完成程序參數的傳遞等等,而後纔是執行具體的程序代碼。雖然C++程序代碼只有一行,可是在彙編視圖下,卻被分解成了多個步驟來完成。主函數的執行,也不過是對於一些寄存器的操做和對庫函數的調用而已。例如,在main()函數的第一句就是用「push ebp」保存當前地址(在彙編代碼中,ebp表明了當前地址)。這裏咱們必定會感到奇怪,爲何在進入main()函數後的第一件事不是在C++程序代碼中看到的輸出一個字符串,而是保存當前地址呢?實際上,咱們從程序代碼中所看到的只是咱們對於要實現的功能的描述,而真正地要實現這些功能,C++程序還要在背後爲咱們完成不少事情。這裏的「push ebp」保存當前地址,就是爲了讓這個main()函數在執行完畢後,能夠順利返回原來的地址繼續往下執行。除了對於寄存器的操做(push、mov以及pop等彙編指令)以外,彙編代碼中更重要的是經過「call」指令完成的對其餘函數的調用。例如,「call __RTC_CheckEsp (0DC132Ah)」這個call指令就是調用__RTC_CheckEsp()函數(由編譯器在調試版本中添加)在程序執行完畢後檢查堆棧是否平衡。
在彙編視圖下,咱們能夠看到每一條C++語句後面都有故事。只有瞭解了每一條語句背後的故事,才能真正地理解這一條語句。這一樣也告訴咱們,若是咱們發現某條語句的行爲出現了異常而咱們又沒法從代碼層面找到緣由,咱們就須要從這條語句的背後尋找真正的緣由。
人們編寫程序的目的,是爲了用程序解決現實世界中的問題。人們觀察發現,全部這些問題都是以數據做爲輸入,而後對這些數據進行處理,最後得到結果數據而使問題獲得解決的。因此,既然我是用來幫助人們解決問題的,那麼個人任務天然也就離不開對數據的描述和對數據的處理。如圖2-8所示。
人們用公式給我下了一個定義:
數據 + 算法 = 程序
其中,數據能夠當作是對現實世界中的各個事物的抽象和描述。例如,在C++程序中,咱們將現實世界中的各類數據抽象成各類數據類型,好比咱們將整數抽象成int類幸,將小數抽象成double類型等。而後反過來用這些類型定義的變量來描述咱們在生活中遇到的某個具體的數據。好比,用int類型定義的變量nWidth來描述某個矩形的寬度;用string類型定義的變量strName來描述某我的的名字;咱們甚至還能夠建立自定義的數據類型來描述更加複雜的事物,好比咱們能夠建立一個Human數據類型來抽象「人」這個復瑣事物,而後用它定義一個變量來描述某個具體的人。總之,用數據對現實世界中的事物進行抽象和描述,是個人第一個任務。
圖2-8 個人人生目的
用數據對現實世界進行描述並非個人最終目的,個人最終目的是對這些數據進行處理,從而得到想要的結果數據。好比,咱們用nWidth和nHeight描述了一個矩形的寬和高,然而,這並非咱們想要的結果數據,咱們想要的是矩形的面積。因此,咱們還必須對nWidth和nHeight這兩個數據進行處理,用「*」符號計算兩個數的乘積,才能得到咱們想要的矩形面積。對數據處理過程的抽象,人們稱之爲算法。而個人第二個任務,就是描述和表達算法,對數據進行處理以得到最終結果。
知道更多:數據結構+算法=程序
「數據+算法=程序」這個等式是由著名的「數據結構+算法=程序」變形而的。它由Pascal之父、結構化程序設計的先驅Niklaus Wirth先生最早提出,它抽象地歸納了一個程序的最核心內容是其中的用於表達數據的數據結構和對數據進行處理的算法。而咱們在這裏提出的「數據+算法=程序」,則是具體地描述了一個程序由它要處理的數據以及對數據進行具體處理的算法共同組成。兩個等式都是正確的,只是描述程序的角度不一樣而已。
數據和算法伴隨個人一輩子。在小小的HelloWorld.exe中,也一樣有數據和算法的存在。例如,向屏幕輸出「Hello World!」的語句:
cout<<"Hello World!"<<endl;
其中,「Hello World!」是一個要向屏幕輸出的字符串數據。整個語句則表明了對這個字符串數據的處理:將字符串顯示到屏幕上。數據和算法老是這樣如影隨行,成爲我終身要完成的兩大任務。