内存空间地址

基本概念

  • 程序员直接接触的内存地址为 虚拟内存地址,而非物理内存地址

  • 以32位系统为例,每个进程都对应4GB虚拟内存地址空间,其中0-3GB为 用户层 ,3-4GB为 内核层

  • 程序员可以直接操用户层,用户层无法直接操作内核层

  • 虚拟内存地址本身不对应任何物理内存或硬盘文件,因此不能存储数据,必须映射到物理内存或硬盘文件,也即分配内存

  • 内存管理的单位是字节,内存映射的基本单位是内存页,一次映射必须是内存页的整数倍

  • getpagesize()可以获取当前系统内存页一页的大小,通常为4096字节

  • 如果不映射而直接使用虚拟内存地址,会引发段错误

  • 对内存进行没有权限的操作也会引发段错误

内存空间区域划分

  • 堆区

    • newdeletemalloc()free()等都在堆区分配和回收内存,堆区内存由程序员手动管理
  • 栈区

    • 存放局部变量、函数形参,由系统自动管理
  • BSS段

    • 存放未初始化的全局变量,在 main() 之前会自动清零
  • 全局区(静态区)

    • 存放已初始化的全局变量、静态变量
  • 只读常量区

    • 存放常量值、const修饰的全局变量,只读,例如C语言字符串的字面值 "abc"
  • 代码区

    • 存放代码,只读

    • 函数指针的值就是函数在代码区的地址

申请内存后系统的响应

    • 只要栈的剩余空间⼤大于所申请空间,系统将为程序提供内存,否则将报异常提⽰栈溢出
    • 操作系统有⼀一个记录空闲内存地址的链表

    • 当系统收到程序的申请时,会遍历该链表,寻找第⼀一个空间⼤大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序

    • 由于找到的堆结点的⼤大⼩小不⼀定正好等于申请的⼤大⼩小 系统会⾃自动的将多余的那部分重新放⼊入空闲链表中

申请大小的限制

    • 栈是向低地址扩展的数据结构是⼀一块连续的内存的区域,栈顶的地址和栈的最⼤大容量是系统预先规定好的
    • 堆是向⾼高地址扩展的数据结构,是不连续的内存区域,这是由于系统是用链表来存储的空闲内存地址的,⾃自然是不连续的,⽽链表的遍历⽅向是由低地址向⾼地址

    • 堆的⼤大⼩小受限于计算机系统中有效的虚拟内存

  • 由此可见,堆获得的空间⽐比较灵活,也⽐比较⼤大

申请效率

    • 由系统自动分配,速度较快
    • 速度较慢,容易产生内存碎片,但是使用方便

malloc()/free()

  • malloc()一次会映射33个内存页,如果一次申请超过33个内存页,则会映射比33个多一点的内存页(系统不同,值不同)

  • malloc()分配内存时,会额外存储一些附加”信息,如分配内存的大小值等,因此其分配的内存地址是不连续的

  • free()只释放虚拟内存地址,将已使用的内存地址标记为未使用,未必会解除映射(最后33个内存页不能接触映射)

  • 使用malloc()分配内存时不要越界使用,否则会损毁附加数据,影响free()

sbrk()/brk()

sbrk.c

#include <stdio.h>
#include <unistd.h>
int main(){
	void *p0 = sbkr(1);//分配1字节内存
	void *p1 = sbrk(4);//再分配4字节内存
	sbkr(0);//取当前位置
	sbrk(-1)://释放1字节
	sbrk(4);//全部释放
	return 0;
}

brk.c

#include <stdio.h>
#include <unistd.h>
int main(){
	void *p0 = sbrk(0);
	brk(p0 + 1);//分配1字节内存
	brk(p0 + 4);//再分配3字节内存,共4字节
	void *p1 = sbrk(0);
	brk(4);//分配4字节
	void *p2 = sbrk(0);
	brk(8);//分配8字节
	void *p3 = sbrk(0);
	brk(p3);//释放p3
	brk(p0);//全部释放
	return 0;
}