内存调用的基本原理
对于计算机的认识,如果仅仅停留在装机时的内存条以及各类内存频率和协议是远不够的,为了深入了解在编程的时候每一个变量
虚拟内存
虚拟内存的基本结构
虚拟内存在存储变量的时候,其主要使用到的结构可以大致分为几个区域,具体如下图所示,以32位操作系统为例:
内存结构 | 备注 | 存储的变量 |
---|---|---|
1G内核空间 | 0XFFFFFFFF | 用户代码不能读写 |
命令行参数 | ||
栈区 | 自顶向下生长,即从高地址向低地址生长 | 存放的是初始化的变量的地址,即int a=0中的a |
… | ||
… | ||
堆区 | 向上生长,即由低地址向高地址生长 | 存放的是由malloc或者new生成的空间,需要手动释放 |
数据段 | 存放全局变量,以及静态数据,所有的static定义的变量全部存在这里 | |
代码段 | 0X00000000 | 存放可执行代码,或只读常量,即字符串"abcd" |
提一嘴:用 _alloca也可以开辟新的空间,但是是在栈上开辟,而不是像malloc和new一样在堆上开辟
虚拟内存简单练习
根据以下代码,分析每个变量在虚拟内存中的位置
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
static int staticVar = 1;
int localVar = 1;
int num1[10] = {1, 2, 3, 4};
char char2[] = "abcd";
char* pChar3 = "abcd";
int* ptr1 = (int*)malloc(sizeof (int)*4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int)*4);
free (ptr1);
free (ptr3);
}
- 选择题:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?C staticGlobalVar在哪里?C
staticVar在哪里?C localVar在哪里?A
num1 在哪里?A
char2在哪里?A *char2在哪里?D
pChar3在哪里?A *pChar3在哪里?D
ptr1在哪里?A *ptr1在哪里?B - 填空题:
sizeof(num1) = 10;
sizeof(char2) = 5; strlen(char2) = 4;
sizeof(pChar3) = 4; strlen(pChar3) = 4;
sizeof(ptr1) = 4;
物理内存
物理内存的定义
物理内存的本质就是我们平时装机时安装的内存条,其本质是DRAM,具体的结构程序员在编程的时候不需要太过在意。
虚拟内存和物理内存
研究虚拟内存和物理内存之间的关系,本质上就是寻找二者之间的对应关系。目前对于二者的对应关系往往有三种,即页式存储、段式存储和段页式存储。
页式存储管理
页式存储靠的就是物理内存和虚拟内存之间的页表,页表的结构可以简化为两个部分,即页号以及页内偏移地址,这两个概念看似很复杂,实际上就和小区里每一户人家的住址一样,页号就像一个大的地址确定一个区间,就像是家住1号楼2单元一样,而页内偏移地址就是一栋楼内的房号,可以在这个区间内找到该变量的起始位置。
页号 | 页内偏移地址 |
---|---|
num1 | 0xa |
num2 | 0xb |
num3 | 0xc |
那如果我已知虚拟内存地址和页表,那如何获得页号和偏移地址呢,计算方法如下:
页
号
=
虚
拟
地
址
/
块
的
大
小
页号=虚拟地址/块的大小
页号=虚拟地址/块的大小
页
内
偏
移
地
址
=
m
o
d
(
虚
拟
地
址
,
块
的
大
小
)
页内偏移地址 = mod(虚拟地址,块的大小)
页内偏移地址=mod(虚拟地址,块的大小)
其中,每一个页的大小为4096个字节
段式存储管理
段式存储和页式存储类似,但是段的大小要比页大,但是表的格式都是一样的,一个是段号,一个是段内偏移。
段号 | 段内偏移地址 |
---|---|
… | … |
段页式存储管理
顾名思义,段页式存储是将段式和页式二者相结合的产物,其中虚拟地址与三个变量有关,分别是段号、页号以及业内偏移
段号 | 页表 |
---|---|
… | … |
先根据段号找到相应的页表,再根据每一个页表找到对应的页号和块号,也就是说,为了管理内存,需要一个段页表,以及多张页表才能找到,看似麻烦的管理方式,但是也更加的灵活。