文章目录
前言
内存是外存与CPU进行沟通的桥梁,包括随机存储器(RAM),只读存储器(ROM),以及高速缓存(CACHE)。
文章简单讲讲内存分配器相关的知识。
一、内存分配器
1.内存块
内存块中,可以装入原生的字节序列,申请者拿到该内存块后可以塑造成整数、浮点数、链表、二叉树等数据结构以及对象、结构体等,这是使用者的事情,同时,我们将维护内存块的分配信息保存在内存块本身中
2.内存分配信息
内存块中取4字节,也就是32比特位中,31个比特位来记录块大小,剩下的一个比特位f/a(free/allocate)用来标识该内存块是否空闲,同时增加一个信息尾,方便释放内存时,可以快速的进行相邻空闲内存块的合并。
二、内存池
1.malloc
(C/C++)申请内存使用的是malloc。
2.内存池技术
内存池技术一次性获取到大块内存,然后在其之上自己管理内存的申请和释放,这样就绕过了标准库以及操作系统。
3.线程局部存储(__thread关键词修饰)
线程局部存储技术,每个线程维护一个内存池。
全局变量在每个线程中都有自己的副本,变量指向的值是线程私有的。
假设这个全局变量是一个整数,变量名字为global_value,初始值为100,那么当线程A将global_value修改为200时,线程B看到的global_value的值依然为100,只有线程A看到的global_value为200,这就是线程局部存储的作用。
三.内存四区
内存四区,分为栈区,堆区,代码区,数据区
- 栈区
栈区由系统自动分配内存,用于存放函数参数,局部变量等,离开作用域自动释放。当函数A调用函数B时,函数B的“第一条”机器指令在A的寄存器里。 - 堆区(heap)
内存动态申请和释放都发生在堆区。
堆区内存由程序员通过new和free关键字主动申请和删除,堆区申请内存需要查找堆区中记录空闲内存地址的链表,遍历该链表找可以放下所需空间大小的内存,并进行记录。 - 代码区
存放函数体内的二进制代码,由操作系统管理,共享,只读 - 全局区
存放全局变量,静态变量以及常量。
四.内存泄漏
以C语言为例:
void memory_leak()
{
int *p = (int *)malloc(sizeof(int));
return;
}
上述代码在申请一段内存后直接返回,这样申请到的这块内存在代码中再也没有机会释放掉了,这就是内存泄漏。
五.CPU的寄存,是怎么装入内存的结构体的?
- Load/Store指令
在精简指令集架构下,会有特定的机器指令,Load/Store指令,来读写内存。
代码:
sum += huge_arr[100];
得到机器指令:
load $r0 100($r2)//加载到寄存器r0中
add $r1 $r1 $r0
第一行指令,数组首地址存放在寄存器r2中,100($r2)表示数组首地址+100,这样我们就能得到huge_arr[100]的地址了,然后将该地址中的值利用load指令加载到寄存器r0中。
第二行指令,r1寄存器中保存的是sum的值,该行指令执行过后r1中的值就已经加上了huge_arr[100]。
编译器把那些经常使用的数据放到寄存器,剩下的放到内存中,然后利用内存读写指令(Load/Store)在寄存器和内存之间来回搬运数据。
六.C语言中的指针
当一个变量不仅仅可以用来保存数值,也可以保存内存地址时,指针诞生了。比如b不需要存a的N个字节,只需要存a在内存中的地址。
举例:
store $1 6
数字1前面加上$符号的就表示数值,否则就是地址。
“@”可以将数值解释为内存地址,例如以下的间接寻址:
load r1 @1
这时该指令会首先把内存地址1中保存的值读取出来发现是3,然后再次把3按照内存地址进行解释,3指向的数据就是变成了a:
地址1 -> 地址3 -> 数据a。
指针是内存地址的更高一级抽象。
-
指针这个概念首次出现在 PL/I 语言中,当时是为了增加链表处理能力。
-
debug
指针指向不存在的变量:
int fun(){
int a = 2;
return &a;
}
void main(){
int *p = fun();
*p = 20;
}
局部变量 a 位于 func 的栈帧中,当 func 执行结束,其栈帧也不复存在,因此 main 函数中调用 func 函数后得到的指针指向一个不存在的变量。
a = &b; 语句就是将b的存储单元的地址存入a存储单元中。
C语言规定*a代表a中存储的地址对应的存储单元中的数据,也就是访问*a就等于访问b,于是*a提供了通过a访问b中的数据的手段。
总结
了解到内存相关的概念后,下一章讲讲进程。