虚拟地址空间以及编译模式

所谓虚拟地址空间,就是程序可以使用的虚拟地址的有效范围。虚拟地址和物理地址的映射关系由操作系统决定,相应地,虚拟地址空间的大小也由操作系统决定,但还会受到编译模式的影响。

这节我们先讲解 CPU,再讲解编译模式,让大家了解编译器是如何配合 CPU 来提高程序运行速度的。

CPU的数据处理能力

CPU 是计算机的核心,决定了计算机的数据处理能力和寻址能力,也即决定了计算机的性能。CPU 一次(一个时钟内)能处理的数据的大小由寄存器的位数和数据总线的宽度(也即有多少根数据总线)决定,我们通常所说的多少位的 CPU,除了可以理解为寄存器的位数,也可以理解数据总线的宽度,通常情况下它们是相等的。
数据总线位于主板之上,不在 CPU 中,也不由 CPU 决定,严格来讲,这里应该说 CPU 能够支持的数据总线的最大根数,也即能够支持的最大数据处理能力,为了表达方便,本文才使用“CPU的数据总线”这一说法。
数据总线和主频都是 CPU 的重要指标:数据总线决定了 CPU 单次的数据处理能力,主频决定了 CPU 单位时间内的数据处理次数,它们的乘积就是 CPU 单位时间内的数据处理量。

我们常常听说,CPU 主频在计算机的发展过程中飞速提升,从最初的几十 KHz,到后来的几百 MHz,再到现在的 4GHz,终于因为硅晶体的物理特性很难再提升,只能向多核方向发展。在这个过程中,CPU 的数据总线宽度也在成倍增长,从早期的 8 位、16 位,到后来的 32位,现在我们计算机大部分都在使用64位 CPU。

需要注意的是,数据总线和地址总线不是一回事,数据总线用于在 CPU 和内存之间传输数据,地址总线用于在内存上定位数据,它们之间没有必然的联系,宽度并不一定相等。实际情况是,地址总线的宽度往往随着数据总线的宽度而增长,以访问更大的内存。

1) 16位CPU

早期的 CPU 是 16 位的,一次能处理 16Bit(2 个字节)的数据。这个时候计算机产业还处在早期,个人电脑也没有进入千家万户,也没有提出虚拟地址的概念,程序还是直接运行在物理内存上,操作系统对内存的管理非常简陋,程序员轻易就能编写一个恶意程序去修改其他程序的内存。

学过汇编的同学应该知道,典型的 16 位处理器是 Intel 8086,它的数据总线有 16 根,地址总线有 20 根,寻址能力为 2^20 = 1MB。

2) 32位CPU

随着计算机产业的进步,出现了 32 位的 CPU,一次能处理 32Bit(4 个字节)的数据。这个时候就提出了虚拟地址的概念,并被应用到 CPU 和操作系统中,由它们共同完成虚拟地址和物理地址的映射,这使得程序编写更加容易,运行更加安全。

典型的 32 位处理器是 Intel 的 80386 和 Intel Pentium 4(奔腾 4):80386 的数据总线和地址总线宽度都是 32 位,寻址能力达 4GB;Pentium 4 的地址总线宽度是 36 位,理论寻址能力达 64 GB。

3) 64位CPU

现代计算机都使用 64 位的 CPU,它们一次能处理 64Bit(8 个字节)的数据。典型的 64 位处理器是 Intel 的 Core i3、i5、i7 等,它们的地址总线宽度为 40~50 位左右。64 位 CPU 的出现使个人电脑再次发生了质的飞跃。

实际支持的物理内存

CPU 支持的物理内存只是理论上的数据,实际应用中还会受到操作系统的限制,例如,Win7  64 位家庭版最大仅支持 8GB 或 16GB 的物理内存,Win7 64 位专业版或企业版能够支持到 192GB 的物理内存。

Windows Server 2003 数据中心版专为大型企业或国家机构而设计,可以处理海量数据,分为 32 位版和 64 位版,32 位版最高支持 512GB 的物理内存,这显然超出了 32 位 CPU 的寻址能力,可以通过两次寻址来实现。

编译模式

为了兼容不同的平台,现代编译器大都提供两种编译模式:32 位模式和 64 位模式。

32位编译模式

在 32 位模式下,一个指针或地址占用 4 个字节的内存,共有 32 位,理论上能够访问的虚拟内存空间大小为 2^32 = 0X100000000 Bytes,即 4GB,有效虚拟地址范围是 0 ~ 0XFFFFFFFF。 

也就是说,对于 32 位的编译模式,不管实际物理内存有多大,程序能够访问的有效虚拟地址空间的范围就是0 ~ 0XFFFFFFFF,也即虚拟地址空间的大小是 4GB。换句话说,程序能够使用的最大内存为 4GB,跟物理内存没有关系。

如果程序需要的内存大于物理内存,或者内存中剩余的空间不足以容纳当前程序,那么操作系统会将内存中暂时用不到的一部分数据写入到磁盘,等需要的时候再读取回来,这在《载入内存,让程序运行起来》中已经讲到。而我们的程序只管使用 4GB 的内存,不用关心硬件资源够不够。

如果物理内存大于 4GB,例如目前很多PC机都配备了8GB的内存,那么程序也无能为力,它只能够使用其中的 4GB。

64位编译模式

在 64 位编译模式下,一个指针或地址占用 8 个字节的内存,共有 64 位,理论上能够访问的虚拟内存空间大小为 2^64。这是一个很大的值,几乎是无限的,就目前的技术来讲,不但物理内存不可能达到这么大,CPU 的寻址能力也没有这么大,实现 64 位长的虚拟地址只会增加系统的复杂度和地址转换的成本,带不来任何好处,所以 Windows 和 Linux 都对虚拟地址进行了限制,仅使用虚拟地址的低 48 位(6 个字节),总的虚拟地址空间大小为 2^48 = 256TB

需要注意的是:
  • 32 位的操作系统只能运行 32 位的程序(也即以 32 位模式编译的程序),64 位操作系统可以同时运行 32 位的程序(为了向前兼容,保留已有的大量的 32 位应用程序)和 64 位的程序(也即以64位模式编译的程序)。
  • 64 位的 CPU 运行 64 位的程序才能发挥它的最大性能,运行 32 位的程序会白白浪费一部分资源。

目前计算机可以说已经进入了 64 位的时代,之所以还要提供 32 位编译模式,是为了兼容一些老的硬件平台和操作系统,或者某些场合下 32 位的环境已经足够,使用 64 位环境会增大成本,例如嵌入式系统、单片机、工控等。
这里所说的 32 位环境是指:32 位的 CPU + 32 位的操作系统 + 32 位的程序。
另外需要说明的是,32 位环境拥有非常经典的设计,易于理解,适合教学,现有的很多资料都是以 32 位环境为基础进行讲解的。本教程也是如此,除非特别指明,否则都是针对 32 位环境。相比于 32 位环境,64 位环境的设计思路并没有发生质的变化,理解了 32 环境很容易向 64 位环境迁移。

开启64位编译模式

目前 VS 的最新版本是 VS2022,创建工程后可以自由切换 32(x86)或者 64(x64)位编译模式,如下图所示:


如果是旧版的 VS,以 VS2010 为例,创建工程后默认是 32 位的,如下图所示:

“Win32”表示 32 位编译模式。如果要以 64 位的方式编译,就需要新增编译模式,如下图所示:

选择“配置管理器”,弹出如下的对话框:

在“活动解决方案平台”下选择“新建”,弹出下面的对话框:

在下拉菜单中选择“x64”,即可新增 64 位编译模式。现在,我们就可以在两种编译模式之间进行切换了:

将下面的代码复制到源文件中:
#include <stdio.h>
#include <stdlib.h>
int a;
int main(){
    int *p = &a;
    printf("%#X, %d\n", p, sizeof(int*));

    system("pause");
    return 0;
}
在 Win32 编译模式下的结果:
0XB715C, 4

在 x64 编译模式下的结果:
0X3FF39740, 8

声明:《C语言系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。