c语言tips-【c语言内存模型】

news2024/11/16 3:20:15

0.摘要

C语言是比较接近底层的语言,因此它的很多知识点是和操作系统挂钩的,例如它的内存模型,其实也是操作系统进程的内存模型,本文章就是解释进程,虚拟内存空间,内存模型的相关知识和它们之间的联系

1. 虚拟内存空间

我们可以把进程所使用的地址「隔离」开来,即让操作系统为每个进程分配独立的一套「虚拟地址」,人人都有,大家自己玩自己的地址就行,互不干涉。但是有个前提每个进程都不能访问物理地址,至于虚拟地址最终怎么落到物理内存里,对进程来说是透明的,操作系统已经把这些都安排的明明白白了。`虚拟地址空间就是一个中间层,相当于在程序和物理内存之间设置了一个屏障,将二者隔离开来。程序中访问的内存地址不再是实际的物理内存地址,而是一个虚拟地址,然后由操作系统将这个虚拟地址映射到适当的物理内存地址上。

  • 首先我们要解释一个概念——进程( Process) 。简单来说,一个可执行程序就是一个进程,前面我们使用 C语言编译生成的程序,运行后就是一个进程。 进程最显著的特点就是拥有独立的地址空间。
  • 严格来说,程序是存储在磁盘上的一个文件,是指令和数据的集合,是一个静态的概念;进程是程序加载到内存运行后一些列的活动,是一个动态的概念。可以类比面对对象,程序是类,进程就是实例化的对象
  • 一个进程对应一个地址空间,而一个程序可能会创建多个进程

2. 虚拟内存空间如何映射到物理内存?

在 CPU 内部,有一个部件叫做 MMU( Memory Management Unit,内存管理单元),由它来负责将虚拟地址映射为物理地址,如下图所示:

在这里插入图片描述

在页映射模式下, CPU 发出的是虚拟地址,也就是我们在程序中看到的地址,这个地址会先交给 MMU,经过MMU 转换以后才能变成了物理地址。

即便是这样, MMU 也要访问好几次内存,性能依然堪忧,所以在 MMU 内部又增加了一个缓存,专门用来存储页目录和页表。 MMU 内部的缓存有限,当页表过大时,也只能将部分常用页表加载到缓存,但这已经足够了,因为经过算法的巧妙设计,可以将缓存的命中率提高到 90%,剩下的 10%的情况无法命中,再去物理内存中加载页表。

有了硬件的直接支持,使用虚拟地址和使用物理地址相比,损失的性能已经很小,在可接受的范围内。

MMU 只是通过页表来完成虚拟地址到物理地址的映射,但不会构建页表,构建页表是操作系统的任务。在程序加载到内存以及程序运行过程中, 操作系统会不断更新程序对应的页表,并将页目录的物理地址保存到 CR3寄存器(CR3 是 CPU 内部的一个寄存器,专门用来保存页目录的物理地址 )。 MMU 向缓存中加载页表时,会根据 CR3 寄存器找到页目录,再找到页表,最终通过软件和硬件的结合来完成内存映射 。

每个程序在运行时都有自己的一套页表,切换程序时,只要改变 CR3 寄存器的值就能够切换到对应的页表。

3. 为什么要用MMU来映射物理内存空间?

  • 每个进程的地址不隔离,有安全风险。

由于程序都是直接访问物理内存,所以恶意程序可以通过内存寻址随意修改别的进程对应的内存数据,以达到破坏的目的。虽然有些时候是非恶意的,但是有些存在 bug 的程序可能不小心修改了其它程序的内存数据,就会导致其它程序的运行出现异常。

  • 内存效率低。

如果直接使用物理内存的话,一个进程对应的内存块就是作为一个整体操作的,如果出现物理内存不够用的时候,我们一般的办法是将不常用的进程拷贝到磁盘的交换分区(虚拟内存)中,以便腾出内存,因此就需要将整个进程一起拷走,如果数据量大,在内存和磁盘之间拷贝时间就会很长,效率低下。

  • 进程中数据的地址不确定,每次都会发生变化。

由于物理内存的使用情况一直在动态的变化,我们无法确定内存现在使用到哪里了,如果直接将程序数据加载到物理内存,内存中每次存储数据的起始地址都是不一样的,这样数据的加载都需要使用相对地址,加载效率低(静态库是使用绝对地址加载的)。

4. 虚拟内存有什么用?

  • 第一,虚拟内存可以使得进程对运行内存超过物理内存大小,因为程序运行符合局部性原理,CPU 访问内存会有很明显的重复访问的倾向性,对于那些没有被经常使用到的内存,我们可以把它换出到物理内存之外,比如硬盘上的 swap 区域。
  • 第二,由于每个进程都有自己的页表,所以每个进程的虚拟内存空间就是相互独立的进程也没有办法访问其他进程的页表,所以这些页表是私有的,这就解决了多进程之间地址冲突的问题。
  • 第三,页表里的页表项中除了物理地址之外,还有一些标记属性的比特,比如控制一个页的读写权限,标记该页是否存在等。在内存访问方面,操作系统提供了更好的安全性。

5. 内存模型是咋样的?

我们在学习c语言时会接触到malloc函数,它是在堆区分配内存空间,那么什么是堆区呢?什么又是栈区呢?

)

这是c语言的内存模型,可以主要分为以下内存区

在这里插入图片描述

内存分区说明
程序代码区 (code存放函数体的二进制代码。一个 C 语言程序由多个函数构成, C 语言程序的执行就是函数之间 的相互调用
常量区 (constant)存放一般的常量、字符串常量等。这块内存只有读取权限,没有写入权限,因此它们的值在程 序运行期间不能改变。
全局数据区 (global data)存放全局变量、静态变量等。这块内存有读写权限,因此它们的值在程序运行期间可以任意改 变
堆区 (heap)一般由程序员分配和释放,若程序员不释放,程序运行结束时由操作系统回收。 malloc()、 calloc()、 free() 等函数操作的就是这块内存,这也是本章要讲解的重点**(注意:这里所说的堆区与数据结构中的堆不是一个概念,堆区的分配方式倒是类似于链表。)**
动态链接库用于在程序运行期间加载和卸载动态链接库
栈区 (stack)存放函数的参数值、局部变量的值等,其操作方式类似于数据结构中的栈
  • 在这些内存分区中(暂时不讨论动态链接库),程序代码区用来保存指令,常量区、全局数据区、堆、栈都用来保存数据。对内存的研究,重点是对数据分区的研究 。

  • 程序代码区、常量区、全局数据区在程序加载到内存后就分配好了,并且在程序运行期间一直存在,不能销毁也不能增加(大小已被固定),只能等到程序运行结束后由操作系统收回,所以全局变量、字符串常量等在程序的任何地方都能访问,因为它们的内存一直都在。

  • 常量区和全局数据区有时也被合称为静态数据区,意思是这段内存专门用来保存数据,在程序运行期间一直存在

  • 函数被调用时,会将参数、局部变量、返回地址等与函数相关的信息压入栈中,函数执行结束后,这些信息都将被销毁。所以局部变量、参数只在当前函数中有效,不能传递到函数外部,因为它们的内存不在了。

  • 常量区、全局数据区、栈上的内存由系统自动分配和释放,不能由程序员控制。程序员唯一能控制的内存区域就是堆( Heap):它是一块巨大的内存空间,常常占据整个虚拟空间的绝大部分,在这片空间中,程序可以申请一块内存,并自由地使用(放入任何数据)。堆内存在程序主动释放之前会一直存在,不随函数的结束而失效。在函数内部产生的数据只要放到堆中,就可以在函数外部使用

一个例子(完美的解决c语言变量的所处内存块问题)

#include <stdio.h>
#include "stdlib.h"

// 字符串在常量区,全局变量在全局区
char *global1 = "junhaozhendeshuai";
// 静态区
static int global2 = 1;
int n;

char* func()
{
    char* str = "张三真的帅!";
    return str;
}



int main()
{
    int a;
    char *str2 = "01234";
    char ptr[20] = "56789";
    char *pstr = func();
    char* c  =  (char *)malloc(100);
    // printf("局部变量初始值为:%d, 全局变量初始值为:%d\n", a, n);
    printf("全局区/static:&global:%p,&global2:%p,n:%p\n", &global1, &global2, &n);
    printf("常量:global1:%p pstr:%p\n",global1,pstr);
    printf("堆区:c:%p\n", c);
    printf("栈区:&str2:%p,&ptr:%p, ptr:%p, a:%p", &str2, &ptr, ptr, &a);
    return 0;
}

输出如下:

全局区/static&global:0x601050,&global2:0x601058,n:0x601060
常量:global1:0x400758 pstr:0x40076a
堆区:c:0x5402040
栈区:&str2:0xfff000ba8,&ptr:0xfff000bc0, ptr:0xfff000bc0, a:0xfff000ba4

总结如下:

  • char *global1 = "junhaozhendeshuai", global1这个指针是在全局区, "junhaozhendeshuai"这个字符串是在常量区,我们要查看global1这个指针的地址可以通过&global1来获得,我们要查看junhaozhendeshuai这个字符串的地址可以通过global1来获得
  • static int global2 = 1;, 这个静态变量global2也在全局区,可以通过&global2获得它的地址
  • int n;,这个变量在函数外定义,在全局区,通过&n来获得地址
  • int a;,这个函数在main函数中,在栈区,通过&a来获得地址
  • char *str2 = "01234";, str2这个指针是在栈区,"01234"这个字符串是在常量区,我们要查看str2这个变量的地址可以通过&str2获得,我们要查看"01234"这个字符串的地址可以通过str2获得,
  • char ptr[20] = "56789";,ptr这个指针是在栈区,"56789"这个字符串也是在栈区,我们要查看ptr这个变量的地址可以通过&ptr获得,我们要查看"56789"这个字符串的地址可以通过ptr获得

为啥两种字符串的表达形式在内存模型不一样呢?
字符串数组char *表示的字符串有什么区别呢?它们最根本的区别是在内存中的存储区域不一样,字符数组存储在全局数据区或栈区,第二种形式的字符串存储在常量区。全局数据区和栈区的字符串(也包括其他数据)有读取和写入的权限,而常量区的字符串(也包括其他数据)只有读取权限,没有写入权限。

  • char *pstr = func();,调用函数一个指向字符串常量的地址,值得注意的是,由于字符串常量已经被建立出来了,它不会被释放,此时我们把指向它的指针返回回来,pstr这个指针是在栈区,可以使用&pstr获得地址,"张三真的帅!"这段字符串是在常量区,可以用pstr获得他的地址
  • char* c = (char *)malloc(100),很明显,用malloc开辟出来的内存是在堆区,即c这个变量的地址在堆区
  • 最后一点,我们可以发现全局整型变量你没初始化时它被赋值为0, 而局部变量你没初始化它被赋值为未知值

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/150923.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

热交换器及一维平行流换热器分析(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 首先试图对热交换器的设置进行建模&#xff0c;并获得该过程的控制方程。使用相应的控制方程并设置边界条件并获得适当的边界值…

RHCE第五天之NFS服务器详解

文章目录一、NFS服务器简介二、NFS的使用三、客户端使用autofs自动挂载四、实验练习一、NFS服务器简介 NFS&#xff08;Network File System&#xff0c;网络文件系统&#xff09;&#xff1a; 是FreeBSD支持的文件系统中的一种&#xff0c;它允许网络中的计算机&#xff08;不…

Qt / Qml 视频硬解码(CUDA)中如何实现无上传硬渲染(一)

【写在前面】 很多时候&#xff0c;我们在对视频的解码和渲染的处理都要经过以下步骤&#xff1a; 软解码&#xff0c;视频帧位于内存。 软渲染&#xff0c;需要拷贝到图像然后渲染&#xff1b;硬渲染则需要上传纹理&#xff0c;然后渲染。硬解码&#xff0c;视频帧位于显存。…

OPengl学习(四)——顶点数组

文章目录1、 问题2、步骤2.1 激活数组2.2 指定数组的数据2.3 解引用和渲染3、例子1、 问题 1、在前面我们实现一个多彩三角形&#xff0c;调用三次glvertext&#xff08;&#xff09;函数&#xff0c;如果在多边形&#xff0c;如20条边的&#xff0c;那么就要使用22次函数&…

【大数据之路】数据管理篇 《三》存储和成本管理 【搬运小结】

文章目录【大数据之路】数据管理篇 《三》存储和成本管理1.1数据压缩1.2存储治理项优化1.3生命周期管理1.3.1 生命周期管理策略1.3.2 生命周期管理策略1.4数据成本计量【大数据之路】数据管理篇 《三》存储和成本管理 1.1数据压缩 在分布式文件系统中&#xff0c;为了提高数据…

python制作课堂点名系统,从此老师对我关爱有加

前言 大家早好、午好、晚好吖 ❤ ~ 准备工作 首先我们需要准备好点名的姓名文件&#xff0c;使用的时候导入进去就可以开始点名了。 新建一个文本文档&#xff0c;将姓名设置设置好&#xff0c;如下&#xff1a; 使用系统库和第三方库都比较常规 from PyQt5.QtWidgets impo…

Attention机制的具体计算过程

一、介绍Query、Key、Value的来源一个输入&#xff0c;经过embedding位置编码后得到最终的输入X&#xff08;512维&#xff09;&#xff0c;最终的输入X与矩阵参数WQ&#xff08;512*64维&#xff09;相乘&#xff0c;得到Query&#xff1b;与矩阵参数WK&#xff08;512*64维&a…

Java反射机制

目录 反射问题的引出 Java程序在计算机中部署的三个阶段 反射的主要相关类 反射机制的优缺点 调优 反射常用类—Class 特点 常用方法 获取映射Class类对象的四种方式 类加载的三个阶段 加载阶段 Loading 链接阶段 Linking 验证 Verification 准备 Preparation 解…

OpenGL示例源码opengl_examples编译

下载好源码并创建编译目录opengl_build 打开CMake-GUI选择源码目录及二进制编译目录:

C#学习记录——【实例】读写ini文件

『知识有两种&#xff0c;一种是你知道的&#xff0c;一种是你知道在哪里能找到的&#xff01;』—— 塞缪尔约翰逊 1、概念 C#读写ini文件之前要了解的概念&#xff1a;INI就是扩展名为"INI"的文件,其实他本身是个文本文件,可以用记事本打开,主要存放的是用户所做…

axios拦截器使用和知识点补充

axios拦截器使用和知识点补充axios拦截器使用axios基地址ajax知识点补充onreadstatechange事件Ajax组成部分了解get请求与post请求区别其他请求方法了解axios拦截器使用 <link rel"stylesheet" href"./lib/bootstrap-v4.6.0.css" /><style>bod…

农业机器人研究进展

文章目录一、农业机器人二、国际农业机器人现状三、我国农业机器人发展情况四、农业机器人展望五、结束语2022年9月17-18日&#xff0c;第十一届中国智能产业高峰论坛成功在厦门举办。大会主论坛上&#xff0c;CAAI副理事长、中国工程院院士、国家农业信息化工程技术研究中心研…

Java实现二叉树

一、树 1、树简介 树是一种非线性的数据结构&#xff0c;具有n个结点其数据存储形式像一棵倒挂的树&#xff0c;树有一个根结点没有前驱结点&#xff0c;树有多个叶子结点没有后继结点&#xff0c;树有多个中间结点既有前驱结点又有后继结点。 树结构中子树之间不能有交集。 n个…

【Node.js实战】一文带你开发博客项目之联调(导入HTML、Nginx反向代理、CORS解决跨域、与前端联调)

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;也会涉及到服务端 &#x1f4c3;个人状态&#xff1a; 在校大学生一枚&#xff0c;已拿多个前端 offer&#xff08;秋招&#xff09; &#x1f680;未…

Tapdata Cloud 场景通关系列: Oracle → MySQL 异构实时同步

【前言】作为中国的 “Fivetran/Airbyte”, Tapdata Cloud 自去年发布云版公测以来&#xff0c;吸引了近万名用户的注册使用。应社区用户上生产系统的要求&#xff0c;Tapdata Cloud 3.0 将正式推出商业版服务&#xff0c;提供对生产系统的 SLA 支撑。Tapdata 目前专注在实时数…

二叉树的遍历(非递归)

二叉树的遍历 遍历二叉树, 是指按一定的规则和顺序访问二叉树的所有结点, 使每一个结点都被访问一次, 而且只被访问一次. 由于二叉树是非线性结构, 因此, 二叉树的遍历实质上是将二叉树的各个结点排列成一个线性序列. DFS: 前序, 中序及后序. BFS: 是指沿着二叉树的宽度优先遍…

Leetcode.1806 还原排列的最少操作步数

题目链接 Leetcode.1806 还原排列的最少操作步数 题目描述 给你一个偶数 ​n​n​n​​​​​ &#xff0c;已知存在一个长度为 nnn 的排列 permpermperm &#xff0c;其中 perm[i]iperm[i] iperm[i]i​&#xff08;下标 从 0 开始 计数&#xff09;。 一步操作中&#xff0…

OLAP和OLTP的区别

OLAP和OLTP的区别 OLAP&#xff1a; (Online transaction processing):在线/联机事务处理。典型的OLTP类操作都比较简单&#xff0c;主要是对数据库中的数据进行增删改查&#xff0c;操作主体一般是产品的用户。 OLTP&#xff1a; (Online analytical processing):指联机分…

Vue新一代状态管理工具—Pinia—都2023年了,快学起来吧!

Pinia 基本介绍 Pinia 是 Vue.js 的轻量级状态管理库 官方网站&#xff1a;https://pinia.vuejs.org/ 中文文档: https://pinia.web3doc.top/introduction.html 为什么学习pinia? pinia和vuex4一样&#xff0c;也是vue 官方 状态管理工具(作者是 Vue 核心团队成员&#xff…

基于JAVA SSM框架的影院管理系统源码,实现包括影院管理,电影管理,影厅管理,排片管理,选座售票,演员管理,影片评论等功能

介绍 下载地址&#xff1a;基于JAVA SSM框架的影院管理系统源码 该项目是一个电影信息管理、发布、展示平台&#xff0c;终端用户可以浏览、购票、评论。项目主要实现包括影院管理&#xff0c;电影管理&#xff0c;影厅管理&#xff0c;排片管理&#xff0c;选座售票&#xff…