為什麼C語言中使用的地址是假的
在C語言中,指標變數的值就是一個記憶體地址,&運算子的作用也是取變數的記憶體地址,請看下面的程式碼:
- #include <stdio.h>
- #include <stdlib.h>
- int a = 1, b = 255;
- int main(){
- int *pa = &a;
- printf("pa = %#X, &b = %#X\n", pa, &b);
- system("pause");
- return 0;
- }
在 C-Free 5.0 下執行,結果為:
pa = 0X402000, &b = 0X402004
程式碼中的 a、b 是全域性變數,它們的記憶體地址在連結時就已經決定了,以後再也不能改變,該程式無論在何時執行,結果都是一樣的。
那麼問題來了,如果實體記憶體中的這兩個地址被其他程式佔用了怎麼辦,我們的程式豈不是無法運行了?
幸運的是,這些記憶體地址都是假的,不是真實的實體記憶體地址,而是虛擬地址。虛擬地址通過CPU的轉換才能對應到實體地址,而且每次程式執行時,作業系統都會重新安排虛擬地址和實體地址的對應關係,哪一段實體記憶體空閒就使用哪一段。如下圖所示:
虛擬地址
虛擬地址的整個想法是這樣的:把程式給出的地址看做是一種虛擬地址(Virtual Address),然後通過某些對映的方法,將這個虛擬地址轉換成實際的實體地址。這樣,只要我們能夠妥善地控制這個虛擬地址到實體地址的對映過程,就可以保證程式每次執行時都可以使用相同的地址。
例如,上面程式碼中變數 a 的地址是 0X402000,第一次執行時它對應的實體記憶體地址可能是 0X12ED90AA,第二次執行時可能又對應 0XED90,而我們的程式不需要關心這些,這些繁雜的記憶體管理工作交給作業系統處理即可。
讓我們回到程式的執行本質上來。使用者程式在執行時不希望介入到這些複雜的記憶體管理過程中,作為普通的程式,它需要的是一個簡單的執行環境,有自己的記憶體,有自己的CPU,好像整個程式佔有整個計算機而不用關心其他的程式。
除了在程式設計時可以使用固定的記憶體地址,給程式設計師帶來方便外,使用虛擬地址還能夠使不同程式的地址空間相互隔離,提高記憶體使用效率。
如果所有程式都直接使用實體記憶體,那麼程式所使用的地址空間不是相互隔離的。惡意程式可以很容易改寫其他程式的記憶體資料,以達到破壞的目的;有些非惡意、但是有 Bug 的程式也可能會不小心修改其他程式的資料,導致其他程式崩潰。
這對於需要安全穩定的計算機環境的使用者來說是不能容忍的,使用者希望他在使用計算機的時候,其中一個任務失敗了,至少不會影響其他任務。
使用了虛擬地址後,程式A和程式B雖然都可以訪問同一個地址,但它們對應的實體地址是不同的,無論如何操作,都不會修改對方的記憶體。
使用虛擬地址後,作業系統會更多地介入到記憶體管理工作中,這使得控制記憶體許可權成為可能。例如,我們希望儲存資料的記憶體沒有執行許可權,儲存程式碼的記憶體沒有修改許可權,作業系統佔用的記憶體普通程式沒有讀取許可權等。
另外,當實體記憶體不足時,作業系統能夠更加靈活地控制換入換出的粒度,磁碟 I/O 是非常耗時的工作,這能夠從很大程度上提高程式效能。
以上兩點我們將在《記憶體分頁機制》和《記憶體分頁機制的實現》中進行詳細講解。
中間層思想
在計算機中,為了讓操作更加直觀、易於理解、增強使用者體驗,開發者經常會使用一件法寶——增加中間層,即使用一種間接的方式來遮蔽複雜的底層細節,只給使用者提供簡單的介面。虛擬地址是使用中間層的一個典型例子。
實際上,計算機的整個發展過程就是不斷引入新的中間層:
- 計算機的早期,程式都是直接執行在硬體之上,自己負責硬體的管理工作;程式設計師也使用二進位制進行程式設計,需要處理各種邊界條件和安全問題。
- 後來人們不能忍受了,於是開發出了作業系統,讓它來管理各種硬體,同時發明了組合語言,減輕程式設計師的負擔。
- 隨著軟體規模的不斷增大,使用匯編語言程式設計開始變得捉襟見肘,不僅學習成本高,開發效率也很低,於是C語言誕生了。C語言編譯器先將C程式碼翻譯為彙編程式碼,再由彙編器將彙編程式碼翻譯成機器指令。
- 隨著計算機的發展,硬體越來越強大,軟體越來越複雜,人們又不滿足於使用C語言了,於是 C++、Java、C#、PHP 等現代化的程式語言誕生了。
轉自:https://blog.csdn.net/qq_28018113/article/details/73437982