本节是操作系统系列教程的第三篇文章,属于操作系统第一章即基础篇,在真正开始操作系统相关章节前在这一部分回顾一些重要的主题,算是温故知新吧,以下是目录,由于本文篇幅较多因此接下来会分三次发布,目录中黑体为本篇内容。
什么是内存
C/C++内存模型
堆区与栈区的本质
Java、Python等内存模型
- Java内存模型
- Jave中的堆区与栈区是如何实现的
- Python内存模型
指针与引用
进程的内存模型
幻想大师-操作系统
总结
1. 什么是内存
内存本身其实非常简单,内存的作用就是用来装数字0和1的,如图所示,图中的一个盒子就是内存的一个基本单元,装的不是0就是1。
- 而内存就是由一大堆的 “盒子”组成,每个盒子中要么是0 要么是1。
- 其中8个盒子被称之为 一个 “字节” ,每8个盒子也就是一个字节他们都有一个编号,这些编号就是简单的从 0开始依次累加,这个编号就是 “内存地址”。
- 你可以把内存地址理解为下面这张图,其中左边的数字是 内存地址,每一排是一个字节,图中展示的就是一个 8字节大小的内存。
- 平时我们使用的 如 2G、4G 大小内存,就是盒子多一点,它在本质上和这里展示的 8字节大小的内存没有任何区别。
- 当计算机在执行我们的程序时,无论是我们的机器指令还是机器指令操作的数据,都需要存放在这些小盒子中(内存中)。
以上就是从硬件的角度来看内存,那么从编程语言上来看,程序员应该如何理解内存了 ?
2. C/C++内存模型
对于C++程序员来说,常用的 int, char 等变量都被装在盒子中,
- char 值只需要一排盒子就能装下(8bit)
- int 值需要 四排盒子才能装下。
- 连续几排装有同样类型变量的盒子就是数组 array
- 连续几排装有不同类型变量的盒子就是结构体 struct
- 每一排盒子的地址就是我们熟知的 指针,请记住,指针就是你使用的变量在内存中的地址。
- C++程序中被执行时,需要在内存中划出两段区域用于存放数据,这两个区域就是我们熟知的 堆(Heap) 和 栈(Stack)。
- 如下图所示:数据段和代码段
- 堆区紧邻数据段,在数据段之上,而栈在最上方,栈和堆之间是尚未被使用的内存,随着程序的运行,当程序申请内存时栈区和堆区之间的空隙会减小,当程序释放后内存间隙会扩大,这就是C++程序内存模型。
2.1 代码段和数据段
编译器的任务就是把人类可以理解的代码转换成机器可以执行的机器指令,源文件编译后形成对应的目标文件。源文件被编译后生成的目标文件本质上只有两部分:
- 代码部分:这里的代码部分指的是集全集可以执行的机器指令,也就是源文件中定义的所有函数。
- 数据部分:源文件中定义的全局变量,如果是已经初始化的全局变量,该全局变量的值也存在于数据部分。
2.2 堆和栈
我们在2.1基础上进一步完善C++程序在内存的样子,细分出 堆和栈结构
- 栈变量:每个函数运行时都会在栈区开辟一块内存,这块内存中保存的是调用函数的桉树以及函数中定义的局部变量,这些变量在函数调用完成后自动释放。
- 堆变量:与栈变量不同,堆变量在使用完之后需要手动释放,否则就会造成内存泄漏。
举例:
#include
void f2()
{
int c;
int* heap;
c = 3;
heap = (int*)malloc(sizeof(int));
*heap = 4;
}
void f1(){
int b;
b= 2;
f2();
}
int main()
{
int a;
a = 1;
f1();
return 0;
}
-
每个函数在被执行的时候都在栈区占用一小段,在这一小段中存放当前函数定义的局部变量和传入函数的参数,这一小段内存有一个很形象名字,叫做 栈帧。
-
这段代码中,main函数会调用 f1 , f1会调用 f2,其中变量a,b,c以及heap 一次被放在各自的函数的栈帧中
-
heap 这个变量本身是在 栈上,但是 heap 所指向的内存分配在 堆上,heap 本身仅仅保存 这个值在内存中的 位置(如 这里 0x10)
.
-
函数栈执行规律遵循着:最先被使用的栈帧其实是最后才被释放的。