已經忘了第一次寫c語言程序到底是什麼時候的事了。不過我卻明白,當時我肯定是知其然而不知所以然。不知從什麼時候開始對程序執行背後的東西感興趣了,而且愈演愈烈,現在終於下定決心去搞明白它了。
就以c語言中最經典的例子來說明吧:
上面的代碼我們保存在helloworld.c文件中。其本質實際上是由0、1的比特(位)序列構成的。8位爲一個字節。每個字節對應某個文本字符。不少系統用ASCII來表示文本字符。實際是由一個唯一的同字節大小的整數值來表示每個字符。下面給出helloworld.c的ASCII表示。
# i n c l u d e <sp> < s t d i o .
35 105 110 99 108 117 100 101 32 60 115 116 100 105 111 46
h > /n /n i n t <sp> m a i n ( ) /n {
104 62 10 10 105 110 116 32 109 97 105 110 40 41 10 123
/n <sp> <sp> <sp> <sp> p r i n t f ( " h e l
10 32 32 32 32 112 114 105 110 116 102 40 34 104 101 108
l o , <sp> w o r l d / n " ) ; /n }
108 111 44 32 119 111 114 108 100 92 110 34 41 59 10 125
以此類推,在計算機系統中,任何介質中的數據都是比特序列。把他們區分成不同的數據對象,是通過數據對象的上下文來確定的。
在這裏,概略地簡述一下c語言源碼從編譯到執行的流程:
程序編譯
程序的編譯過程如下圖所示,分爲預處理、編譯、彙編、鏈接等幾個階段。
預處理:預處理過程主要處理那些源代碼文件hello.c中的以“#”開始的預編譯指令。比如“#include“、“#define”等,主要處理規則如下:
1. 將所有的“define”刪除,並且展開所有的宏定義。
2. 處理所有的條件預編譯指令,比如“#if”、“#ifdef”、“#elif”、“#else”、“#endif".
3. 處理“#include”預編譯指令,將被包含的文件插入到該預編譯指令的位置。注意,這個過程是遞歸進行的,也就是說被包含的文件可能還包含其他文件。注:這裏的文件僅僅指的是頭文件而非c文件。
4. 刪除所有的註釋“//”和“/* */”。
5. 添加行號和文件名標識,比如#2 "hello.c" 2,以便於編譯時編譯器產生調試用的行號信息及用於編譯時產生編譯錯誤或警告時能夠顯示行號。
6. 保留所有的#pragma編譯器指令,因爲編譯器需要使用他們。
以下爲hello.i的內容(樣板):
編譯: 編譯過程就是把預處理完的文件進行一系列詞法分析、語法分析、語義分析及優化後生成的彙編代碼文件。
彙編: 將彙編文件翻譯成機器指令,並打包成可重定位目標程序的O文件。該文件是二進制文件,字節編碼是機器指令。
鏈接: 將引用的其他O文件併入到我們程序所在的o文件中,處理得到最終的可執行文件
硬件組成:
從下圖中看出一個典型的系統由總線、Cpu、I/O設備、主存等構成。
在這裏我們只是先睹爲快,後面真正執行時會大量設計cpu與主存、磁盤的交互。。。
程序的運行:
程序的鏈接、裝載及運行非常複雜,下一篇將絲絲入扣地剖析它。。。