C++知识——4、malloc内存分配

一、初识:

调用malloc接口分配一段连续的内存空间,不使用时使用free可以释放这段内存空间。

二、定义:

标准C的定义

,malloc的函数原型为:

<span style="font-family:Microsoft YaHei;font-size:14px;">void* malloc(size_t size);</span>

函数要求如下:

  1. malloc函数分配的内存大小至少为size参数所指定的字节数。
  2. malloc的返回值是一个void类型的指针,我们必须强制转化为我们需要的类型的指针
  3. 多次调用malloc所分配的地址不能有重叠部分,除非某次molloc所分配的地址被free释放掉了。
  4. malloc应该尽快的完成内存额分配并且返回。
  5. 实现malloc的同时实现calloc和realloc和free。

在Linux环境下为

<span style="font-size:14px;">man malloc
</span>

三、Linux的内存管理:

1、虚拟内存地址与物理内存地址的关系:

现代操作系统在处理内存地址时普遍采用虚拟内存地址技术,那什么是虚拟内存技术?

这种技术使每个进程仿佛“独享”一块2N字节的内存(N是机器的位数),例如在64位CPU和64位操作系统下,每个进程的虚拟内存空间是264Byte。这种虚拟内存空间的作用是简化程序的编写并且方便操作系统对进程之间的隔离管理。
虚拟内存技术是MMU页表构成的MMU是一种映射算法,它从虚拟内存地址映射到物理内存地址上,单位是页
C++知识——4、malloc内存分配
内存地址可以分为页号p页内偏移量w

2、什么是页表:

在现代操作系统中,虚拟内存地址、物理内存地址,都是以为单位管理的,而不是大家以为的字节。(一个内存页是一段固定的地址,典型的内存页的大小是4K)。

四、Linux进程级的内存管理

一个进程的内核空间:
C++知识——4、malloc内存分配
各部分分别是:

  • 正文段(Text Segment):这是整个用户空间的最低地址部分,存放的是指令(也就是程序所编译成的可执行机器码)【可执行程序的二进制镜像】;
  • 数据段(Data Segment):这里存放的是初始化过的静态变量和全局变量;
  • 符号段(BSS, Block started symbol):这里存放的是未初始化的全局变量和静态的局部变量;
  • 堆(Heap):这是我们本文重点关注的地方,堆自低地址向高地址增长,后面要讲到的brk相关的系统调用就是从这里分配内存
  • 栈(Stack)自高地址向低地址增长
  • 命令行参数和环境变量:用户调用的最底层。

我们都知道,在malloc分配空间时是在Heap上分配的,实质上,Linux维护一个break指针,这个指针指向堆空间的某个地址。从堆起始地址到break之间的地址空间为映射好的,可以供进程访问;而从break往上,是未映射的地址空间,如果访问这段空间则程序会报错
C++知识——4、malloc内存分配
由上文知道,要增加一个进程实际的可用堆大小,就需要将break指针向高地址移动。Linux通过brksbrk系统调用操作break指针。两个系统调用的原型如下:

int brk(void *addr);
void *sbrk(intptr_t increment);

brk:将break指针直接设置为某个地址;
sbrk:将break从当前位置移动increment所指定的增量。

brk在执行成功时返回0,否则返回-1并设置errno为ENOMEM;sbrk成功时返回break移动之前所指向的地址,否则返回(void *)-1。

一个小技巧是,如果将increment设置为0,则可以获得当前break的地址。

另外需要注意的是,由于Linux是按页进行内存映射的,所以如果break被设置为没有按页大小对齐,则系统实际上会在最后映射一个完整的页,从而实际已映射的内存空间比break指向的地方要大一些。但是使用break之后的地址是很危险的(尽管也许break之后确实有一小块可用内存地址)。

注:更多请参考文章