#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;}
程序运行结果为: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 等当代化的编程措辞出身了。