讀書筆記_深入理解計算機系統_第1章_計算機系統漫遊 (代碼編譯鏈接詳細過程)

第一章:計算機系統漫遊


信息是什麼?昨天和同學走的時候,正好就說起了這個話題,“信息就是概率”,同學如是說。那麼信息在計算機裏是什麼呢?總不能還說是概率吧,計算機可不懂什麼概率。

在本書第一章第一頁的標題上赫然寫着“信息就是位+上下文”。什麼是位?位就是比特,就是二進制。計算機裏沒有概率,有的只是一連串0或1的序列。那什麼是上下文?這個跟我們經常在英文閱讀理解裏遇到的“上下文”是差不多的,舉個例子,二進制數10000011,對應十六進制的0x83,對應十進制的131,儘管在計算機裏存的是10000011,但根據不同的“上下文”,卻有着不同的含義,若把它當作指令,則表示“add”,是對兩個操作數求和,若把它當作short短整型,則表示131,它還有可能是double型的一部分……總之,計算機裏實實在在存的都是一些二進制碼,應該把它解釋成什麼,則要根據“語境”。

第一章中我認爲比較重要的有兩個部分,一個是可執行程序的誕生過程,描述由一個高級語言源文件(比如C、C++或Java文件)經過加工走到可執行程序的過程;另一個是操作系統存放數據的位置,比如堆和棧等,這個在面試中常常作爲操作系統的基礎知識去考察,若這個也不清楚的話,面試官就認爲你一點也不懂操作系統了。

先談談一個可執行程序的誕生過程。現在有了很方便的IDE(集成開發環境),比如我們常用的Visual Studio(簡稱VS),還有eclipse等等,已經淡化了從一個文本文件到可執行程序的過程。初學者在VS環境下輸入代碼,然後單擊編譯連接按鈕:

若沒有語法錯,可執行程序就生成完畢了,卻不知這個按鈕按下後,發生了哪些事情。

 

如上圖所示,現在編程大部分都是用高級語言(因爲可移植性好,自己和別的程序猿也容易看的懂),我們在稱之爲源文件的地方書寫代碼,如下圖所示。

以主流的C/C++爲例,若是用C語言寫的源文件(source program),源文件後綴一般是.c,若是用C++語言寫的源文件,則後綴一般是.cpp(不要小看這個後綴,有時調程序時,出現很多離奇的錯誤往往就是這個後綴造成的,因爲如果你用.c作爲後綴,那麼當你的程序裏出現了class等關鍵字的時候,編譯器就認不出來了,另外,C語言的行文要求也比C++嚴格,C++語法通過的地方,C可能反而通不過)。

在編譯器編譯前,其實還有一個預編譯(pre-process)的過程,在預編譯時,宏會被展開,一些常義變量也會被替換爲常量。這裏就多談談預編譯的問題:關於宏定義,一定要多寫寫括號,不然展開就會有問題,比如

#define HELLO 3+5

這個看似和定義

#define HELLO 8

差不多,其實差遠了,請看

int hi = HELLO *3;

現在問hi是什麼?是24嗎?我們不妨展開來看,預編譯器是這樣做的:

int hi = 3 + 5 * 3

hi其實是18!

這個問題看似比較簡單,但卻是筆試題中常考的地方。

還有常義變量,比如

const int count = 3;

int *p = (int*)&count;

*p = 5;

cout << count << “ ” << *p << endl;

現在問輸出的是什麼?這是今年中興筆試的題(類似,具體數字忘了),老實說,這道題我當時糾結了不少時間,總是覺得*p = 5 會報錯,其實不然。程序是可以執行的,且輸出一個是3,一個是5,爲什麼會這樣?

原來這是一個常量替換(更正式的說法是常量摺疊),在預編譯時,count就被替換爲3了,所以輸出的語句實際上早就變成了

cout << 3 << “ “ << *p << endl;

那爲什麼可以修改常量的值?這是因爲強制類型轉換將const int* 轉成了 int*。

言歸正傳,由預處理器生成的文件是hello.i,接下來交由編譯器來編譯,編譯後得到彙編程序文件hello.s,彙編程序與機器碼已經可以一一對應了,彙編器將彙編程序文件轉成目標文件hello.o,在hello中還會使用到來自本程序“外部”的函數,比如printf,它位於系統提供的庫中,外部庫也有目標文件,比如這裏的printf.o,鏈接器將hello.o和printf.o鏈接到一起生成可執行的二進制文件(在windows下一般是用.exe結尾的,但在linux下不一定,取決於你的定義,這裏就直接是hello了,在linux環境中當前目錄的命令行下輸入“./hello”,就可以執行這個程序了)。再宏觀地看一眼這個流程,我們程序員可以看懂的文件(ASCII碼文件)包括高級語言源文件(如hello.c)、預編譯文件(如hello.i)和彙編程序文件(如hello.s),看不懂的文件(二進制文件,打開後都是亂碼)包括目標文件(如hello.o)和可執行程序文件(如hello)。

最後談談操作系統的基礎知識,也就是程序內存中的分佈,以windows操作系統爲例,地址空間從高到低依次是數據段、代碼段、堆和棧。數據段存放程序的全局變量和靜態變量,堆存放動態分配的內存(在C++中是new出來的內存,C中是malloc出來的內存),代碼段顧名思義是存放程序的機器碼,棧則存放局部的變量。在360面試的時候,面試官就問棧的生長方向,是向低地址區生長還是高地址區生長?即對一個棧push(x)後再push(y),x和y在內存中的地址孰高孰低?這個需要記憶一下,棧在堆的下面(“下”表示地址更低),且棧是向低地址方向生長,而堆在棧的上面(“上“表示地址更高),且堆是向高地址方向生長,兩者生長方向是背離的。注意linux的內存分佈與windows不同。

下面在VS中驗證win32環境下的內存分佈,代碼如下:

複製代碼
 1 int main()
 2 {
 3     int b = 3; //
 4     int *c = new int[2]; // 5 
 6     // 輸出觀察地址空間
 7     cout << "靜態變量地址 " << &a << endl;
 8     cout << "全局變量地址 " << &d << endl;
 9     cout << "常量地址(與代段存在一起)" << &hello << endl;
10     cout << "棧地址 " << &b << endl;
11     cout << "堆地址1 " << &c[0] << "堆地址2 " << &c[1]<<endl;
12     delete [] c;
13 
14     int ss = 4;
15     int sss = 5;
16     cout << "第一個棧元素地址 " << &ss << endl;
17     cout << "第二個棧元素地址 " << &sss << endl;
18 }
複製代碼

運行結果爲:


轉自 http://www.cnblogs.com/jerry19880126/

另在linux試驗下ss的地址也大於sss的地址,但是我在compilr.com上運行的結果卻不一致,供大家參考:






發佈了41 篇原創文章 · 獲贊 19 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章