C++编译和内存模型

c++编译

以hello.c 程序为例

# include <stdio.h>
main{
    printf("hello\n");
}

image-20220816161649569

一个.c源程序需要经过预处理器生成.i文件,再经过编译器生成.s文件,再经过汇编器生成可重定位目标文件.o文件,再与其他.o文件经过链接器生成最终的可执行目标程序。

过程概述

  1. 预处理阶段

    进行宏定义、头文件的替换、处理条件预编译。

  2. 编译阶段

    将预处理文件进行语法分析,词法分析,语义分析,生成汇编代码文件。

  3. 汇编阶段

    利用汇编程序(汇编器)将汇编语言源程序转换成机器指令序列(机器语言程序)。

  4. 链接阶段。

    将多个可重定位的目标文件.o合并以生成可执行文件,其可以被加载到内存中,由系统执行。

静态链接和动态链接

静态链接

在程序执行之前完成所有目标文件的链接,生成一个可执行的目标文件(EXE文件)。

动态链接

在程序执行时,部分目标文件才被装入内存之后完成链接工作,更节省内存。

内存空间模型

内核空间和用户空间

在 Linux 操作系统中,虚拟地址空间的内部又被分为内核空间和用户空间两部分,不同位数的系统,地址空间的范围也不同。比如最常见的 32 位和 64 位系统,如下所示:

image-20220816163350836

通过这里可以看出:

  • 32 位系统的内核空间占用 1G,位于最高处,剩下的 3G 是用户空间;
  • 64 位系统的内核空间和用户空间都是 128T,分别占据整个内存空间的最高和最低处,剩下的中间部分是未定义的。
  • 虽然每个进程都各自有独立的虚拟内存,但是每个虚拟内存中的内核地址,其实关联的都是相同的物理内存。这样,进程切换到内核态后,就可以很方便地访问内核空间内存。

用户空间

image-20220816163420732

  • 代码段(.text),包括二进制可执行代码;
  • 已初始化数据段(.data),包括静态常量;
  • 未初始化数据段(.bss),包括未初始化的静态变量;
  • 堆段,包括动态分配的内存,从低地址开始向上增长;
  • 文件映射段,包括动态库、共享内存等,从低地址开始向上增长(跟硬件和内核版本有关 (opens new window));
  • 栈段,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。当然系统也提供了参数,以便我们自定义大小;

malloc 函数

malloc分配内存方式

实际上,malloc() 并不是系统调用,而是 C 库里的函数,用于动态分配内存。malloc 申请内存的时候,会有两种方式向操作系统申请堆内存。

  • 方式一:如果用户分配的内存小于 128 KB,则通过 brk() 函数将「堆顶」指针向高地址移动,获得新的内存空间。

    image-20220816170600475

  • 方式二通过如果用户分配的内存大于 128 KB, mmap() 系统调用中「私有匿名映射」的方式,在文件映射区分配一块内存,也就是从文件映射区“偷”了一块内存。如下图:

    image-20220816170624228

malloc() 的具体实现

每次先申请一大块内存,然后划分为不同大小的区块,把相同大小的空闲区块以双向链表形式组织起来为空闲链表。

malloc分配的是物理内存吗?

不是的,malloc() 分配的是虚拟内存。

如果分配后的虚拟内存没有被访问的话,是不会将虚拟内存不会映射到物理内存,这样就不会占用物理内存了。

只有在访问已分配的虚拟地址空间的时候,操作系统通过查找页表,发现虚拟内存对应的页没有在物理内存中,就会触发缺页中断,然后操作系统会建立虚拟内存和物理内存之间的映射关系。

free 释放内存,会归还给操作系统吗?

  • malloc 通过 brk() 方式申请的内存,free 释放内存的时候,并不会把内存归还给操作系统,而是缓存在 malloc 的内存池中,待下次使用
  • malloc 通过 mmap() 方式申请的内存,free 释放内存的时候,会把内存归还给操作系统,内存得到真正的释放。

mmap映射?

mmap主要实现方式是将磁盘数据直接映射到用户缓冲空间,从而减少了从内核缓冲区到用户缓冲区的一次CPU拷贝。

posted @ 2022-08-26 15:11  xfw121  阅读(56)  评论(0)    收藏  举报