大话C语言:第27篇 内存模型

1 存储硬件概述

现代计算机遵循冯诺依曼体系结果,存储分为:

  • 外部存储器:长期存放数据,掉电不丢失数据。例如,硬盘、flash、rom、u 盘、光盘、磁带。

  • 内部存储器:暂时存放数据,掉电数据丢失。例如,DDR内存条

应用程序从外部存储器加载至内部存储器过程包括:

  • **用户启动应用程序:**用户通过点击图标、从命令行运行或其他方式启动应用程序。

  • **操作系统响应:**操作系统接收到启动请求,并开始处理;如果应用程序尚未在内存中,操作系统会查找应用程序在磁盘上的位置(通常是可执行文件)。

  • **加载程序(Loader)工作:**加载程序首先读取应用程序的头部信息,这通常包括程序所需的各种资源、依赖项和代码段的位置。

  • **读取代码和数据:**加载程序从磁盘读取应用程序的代码段、数据段和其他必要的段。这些段通常包括程序的指令(即代码)和程序运行所需的数据。

  • **分配内存空间:**操作系统为应用程序分配所需的内存空间。这通常涉及到管理物理内存和虚拟内存。

  • **加载至内存:**将从磁盘读取的代码和数据段加载到分配的内存空间中。如果内存空间不足以容纳整个应用程序,操作系统会使用虚拟内存技术,将部分应用程序存储在磁盘上,并在需要时将其交换到RAM中。

  • **设置程序计数器和其他寄存器:**操作系统设置程序计数器(PC)以指向程序的入口点(即程序开始执行的指令)。还会设置其他必要的寄存器,以支持程序的执行。

  • **执行:**一旦程序在内存中准备好,操作系统会将控制权交给应用程序,程序开始执行。

2 物理内存和虚拟内存

  • 物理内存:实实在在存在的存储设备

  • 虚拟内存:内存管理的一种技术,它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间)。

其中,虚拟内存的工作原理是将进程使用的内存分为多个页面(通常为4KB或8KB),每个页面都有一个唯一的虚拟地址。当进程需要访问某个页面时,操作系统会检查该页面是否已经在物理内存中。如果已经在内存中,则直接访问该页面;如果不在内存中,则操作系统会将该页面从磁盘上的虚拟内存中读取到内存中,并将其映射到进程的虚拟地址空间中。

操作系统会在物理内存和虚拟内存之间做映射。

其中,虚拟内存主要有以下几种类型:

  • 分页式虚拟存储:物理内存和虚拟内存都被划分为固定大小的页面(Page)。当程序需要更多内存时,操作系统将不常用的页面移动到硬盘上的交换空间,并将需要的页面加载到物理内存中。这种方式实现了内存的动态分配和页面的调度,但需要频繁地进行页面调入和调出,可能影响系统性能。

  • 段式虚拟存储:程序的地址空间被划分为多个逻辑段,每个段可以具有不同的长度和访问权限。当程序需要内存时,操作系统将程序的逻辑段映射到物理内存或硬盘上的交换空间。这种方式更灵活,可以根据程序的需要分配不同大小的内存空间,但需要额外的管理和调度。

  • 请求分页式虚拟存储:结合了分页式和段式虚拟存储的特点。在这种方式中,程序的地址空间被划分为多个段,每个段又被划分为多个页面。

3 逻辑地址和物理地址

  • **逻辑地址:**是指程序在运行过程中使用的地址,也称为虚拟地址(Virtual Address)。它是由CPU生成的,用于访问内存中的数据。逻辑地址的大小和位数取决于处理器的架构和操作系统的设计,通常是一个定长的二进制数值。在执行指令时,CPU通过将逻辑地址转化为物理地址来获取数据。

  • **物理地址:**是指内存中实际的地址,也称为实地址(Real Address)。物理地址表示内存模块中每个存储单元(通常是字节)的唯一标识符,因此具有唯一性,且直接与内存相关联。物理地址通常是一个以十六进制表示的数字,它确定了计算机中的实际内存位置。

其中,应用程序主要使用的是逻辑地址;逻辑地址是程序代码中使用的地址,由程序员或操作系统生成。在 32 位系统下,每个进程(运行着的程序)的寻址范围是 0x00000000 ~0xff ff ff ff,空间大小为4G。

4 C语言程序内存布局

4.1 系统空间

存放在整个内核的代码和所有的内核模块,用来内核空间执行 Linux 系统调用。

4.2 栈区

存局部变量、函数,调用函数时会开辟栈区,函数结束时就自动回收,遵循后进先出的原则,从高地址向低地址增长。

栈区主要特点包括:

  • 栈内存由编译器在程序编译阶段完成。

  • 函数返回后该函数的栈空间消失,所以函数中返回局部变量的地址都是非法的。

  • 栈区存放局部变量。

  • 堆区的空间是由下往上增长的。

  • 自动分配内存,{}内有效,离开{}自动释放。

  • 未初始化的值为随机值。

  • 未初始化的静态局部变量,其值为0

4.3 堆区

malloc、realloc、calloc等开辟的内存就在堆,从低地址向高地址增长,由程序员分配和释放,系统不自动回收,所以一定要记得申请了就要释放,以免溢出。动态内存是开发者手动分配的,是堆分配的。

堆的主要特点:

  • 堆内存是在程序执行过程中分配的,用于存放进程运行中被动态分配的的变量。

  • 函数返回这段内存不会消失。

  • 动态申请的内存,程序员自己管理,用完要free,否则内存泄漏。

  • 堆区的空间是由下往上增长的。

  • 内存里面的内容为随机值,一般用memset函数清0。

注意,

  • 避免分配大量的小内存块。分配堆上的内存有一些系统开销,所以分配许多小的内存块比分配几个大内存块的 系统开销大。

  • 仅在需要时分配内存。只要使用完堆上的内存块,就需要及时释放它(如果使用动态分配内存,需要遵守原则: 谁分配,谁释放), 否则可能出现内存泄漏**。**

4.4 数据段

数据段包含程序中已经初始化的全局变量和静态变量。这些变量在程序运行期间一直存在,并且它们的值在程序开始执行之前就已经确定。

数据段可以进一步细分为已初始化数据段(包含有明确初始值的全局变量和静态变量)和未初始化数据段(也称为BSS段,包含未明确初始化的全局变量和静态变量,通常初始化为0)。

数据段分为:

  • **rodata段:**Read-Only Data段的缩写,是程序内存中的一个特定区域,用于存放只读数据,也就是那些不可修改的常量数据。这些数据在程序执行期间不会发生变化,因此被设计为只读,以防止程序意外地修改它们。rodata段常被称为常量区,存放的是诸如整数常量、字符串常量等。

  • **bss段:**程序内存布局中的一个特定段,它主要用于存放程序中未初始化的全局变量和静态变量。这些变量在编译时并没有明确赋予初值,因此在程序加载到内存时,系统会自动将bss段的内存空间清零,以保证这些变量在使用前具备确定的初值。

  • **data段:**主要用于存储程序中已经初始化且初值不为0的全局变量和静态局部变量。这些数据在程序编译时就已经确定,并且在程序运行期间会保持不变,因此它们被存放在一个可读可写的内存区域中。

注意,data段与bss段在功能上有所区别:

  • bss段主要用于存储未初始化的全局变量和静态变量,这些变量在程序加载时会被自动初始化为0。而data段则专门用于存储已初始化的非零变量。这种分工使得内存管理更为高效和有序。

  • 在程序执行过程中,data段的内容是保持不变的。这是因为这些数据在程序编译时已经确定,并且在程序运行期间不会被修改。这使得.data段成为程序中的一个稳定的数据存储区域,为程序提供了可靠的数据支持。

4.5 文本段

文本段,也称为代码段或简称为文本,是目标文件或内存中的程序段之一,其中包含可执行指令。作为内存区域,可以将文本段放置在堆或堆栈下方,以防止堆和堆栈溢出覆盖它。

通常,文本段是可共享的,因此对于频繁执行的程序(例如文本编辑器、C 编译器、shell 等),只需要在内存中保存一个副本。此外,文本段通常是只读的,以防止程序意外修改其指令。

相关推荐
pas1366 分钟前
41-parse的实现原理&有限状态机
开发语言·前端·javascript
Gogo8168 分钟前
BigInt 与 Number 的爱恨情仇,为何大佬都劝你“能用 Number 就别用 BigInt”?
后端
fuquxiaoguang9 分钟前
深入浅出:使用MDC构建SpringBoot全链路请求追踪系统
java·spring boot·后端·调用链分析
琹箐17 分钟前
最大堆和最小堆 实现思路
java·开发语言·算法
中议视控19 分钟前
可编程网络中央控制系统主机通过红外发射棒控制空调电视等红外设备
网络·物联网·5g
renhongxia11 小时前
如何基于知识图谱进行故障原因、事故原因推理,需要用到哪些算法
人工智能·深度学习·算法·机器学习·自然语言处理·transformer·知识图谱
坚持就完事了1 小时前
数据结构之树(Java实现)
java·算法
星马梦缘1 小时前
EDA彩灯电路绘制
单片机·嵌入式硬件·物联网·pcb·eda·嘉立创
算法备案代理1 小时前
大模型备案与算法备案,企业该如何选择?
人工智能·算法·大模型·算法备案
Monly211 小时前
Java:修改打包配置文件
java·开发语言