C++语法|进程虚拟地址空间和函数调用栈

本文来自施磊老师的课程,老师讲的非常不错,我的笔记也是囫囵吞枣全部记下,但是我在这里推荐一本书,真的真的建议初学C++或者想要进阶C++的同学们看看:《CPU眼里的C/C++》

文章目录

  • 进程的虚拟地址空间和布局
    • 进程虚拟地址空间
      • 1 不可访问区域
      • 2 .text代码段和.rodata只读数据段
      • 3 .data数据段和.bss数据段
      • 4 .heap段
      • 5 *.dll *so库
      • 6 stack段
      • 7 命令行参数和环境变量
      • 8 内核空间
    • 代码分析
  • 重点问题
    • 为什么局部变量一会儿说在栈上,一会儿又是在 .text段
    • 每一个进程的用户空间是私有的,但是内核空间是共享的!!!
  • 函数调用栈
    • 代码运行过程
      • int a = 10;
      • int b = 20
      • int ret = sum(a, b)
      • 回到main函数
    • 请回答本节开头的两个问题

进程的虚拟地址空间和布局

任何的编程语言,无非产生两种东西:指令和数据。
在任何操作系统,程序编译链接完成后,会生成可执行文件,并且该文件会存储在磁盘当中,在我们执行该文件,它就会加载到内存中。那么我们就有一个疑问:内存到底有没有区域的划分,划分之后又是什么样子呢?

不过就算我们加载到内存,也不可能加载到物理内存!!!

加载到内存中,首先linux系统(x86 32位)会给当前进程分配一个 2 3 2 2^32 232(4G)大小到一块空间。

需要注意的是,比较常见的画法是,低地址在下,高地址在上

这个空间叫做进程的虚拟地址空间,其实虚拟地址空间的本质不过是内核创建的一系列数据结构。

NOTE:
它存在,你能看见,它是物理的
它存在,你不能看见,它是透明的
它不存在,你能看见,它是虚拟的
它不存在,你也看不见,它被删除了

进程虚拟地址空间

该空间被默认分为两部分,一部分从0x00000000~0xC0000000一共3G到校被称为user space用户空间,剩下的空间为kernal space为1G。

每一个进程都有这么一个虚拟地址空间,在用户空间的划分情况又是什么样的呢?

1 不可访问区域

它并没有从零地址开始存储,而是从`0x08048000`开始存储,所以最顶部的空间是不能够访问的。有些情况下如果我们访问控制真: ```cpp char *p = nullptr; strlen(p); char *src = nullptr; strcpy(dest, src); ``` 这些都是零地址,其实就是在我们的`0x00000000`~`0x08048000`这部分地址不允许访问,不能读也不能写。如果访问的话程序会崩溃,系统要报异常(通过信号)。

2 .text代码段和.rodata只读数据段

0x08048000开始,首先是.text如果有人问指令在运行的时候放在哪块区域,我们不要说全局变量区或者静态区,直接说代码段或者.text段即可****。这一部分通常还有一块区域叫做.rodata叫做只读数据段放的是什么呢?

//在函数中定义一个局部变量指针
char *p = "hello world"; //会报warning
//只能写成
const chart *p = "hello world";

对于本例来说,p就在栈上,p指向的那个常量字符串就在.rodata段,那么如果我们想修改这个指针*p = 'a',这样编译是没有问题的,但是如果运行这个程序会直接挂掉,因为.rodata.text段落只能读不能写。其实在C++较新的编译器中,是不能使用普通指针指向常量字符串的(会报warning)。如果我们使用const修饰,所以就不会发生*p='a'这样不可预期的错误了。

3 .data数据段和.bss数据段

这两个段落都叫数据段,那么这两个有什么不同呢?

.data只存放初始化过的并且初始化数据不为0的

.bss存放未初始化的以及初始化为0的,程序运行的时候会把该段数据全部初始化为0。

那么,当我们全局作用域中,写一个全局变量但是没有初始化,当我们去打印它的值会发现它是一个0,程序运行的时候我们内核给当前进程分配地址空间,我们程序未初始化的数据放在.bss,我们的内核也就是操作系统会自己负责把.bss段的数据全部置为0,这就是为什么未初始化的全局变量是0。

#include <iostream>
using namespace std;
int gdata;
int main() {   cout << gdata << endl; //被内核初始化为0return 0;
}

4 .heap段

.bss段落再往下,暂时还没有,但是我们先把它画出来,这块空间就叫做堆heap!.heap只有在我们调用了newmallocalloc才被分配空间。

5 *.dll *so库

堆内存再往下就是我们当前程序在运行过程中会加载一些共享库,也就是我们的动态链接库,windows下是*.dll,linxu下是*.so库。再一个需要注意的是,这里也是我们堆栈共享区,栈会向低地址生长,堆则会向高地址生长。
比较常见的画法应该是下面是低地址,上面是高地址

6 stack段

现在就到我们的栈空间!程序运行每一个线程都独有的stack栈空间!栈空间跟其他地方不一样的是,栈空间是从下往上进行增长,堆被分配时是从低地址到高地址的增长。

7 命令行参数和环境变量

在这里存储命令行参数和环境变量的路径


8 内核空间

此处为内核空间,内核空间是进程共享的!!!!
以上就是我们用户空间内存划分的布局,在内核空间主要分为了ZONE_DMA和ZONE_NORMAL还有ZONE_HIGHMEM这三块地区。大概分别为16M、800M、剩下的就是我们的ZONE_HIGHMEM
在ZONE_NORMAL一般是放PCB块,以及内核空间的线程和内核空间运行的函数所在的栈空间都在这一部分。
最后ZONE_HIGHMEM是高端内存,它是映射我们高地址的物理内存的时候做地址映射用的。

代码分析

int gdata1 = 10;
int gdata1 = 0;
int gdata3;static int gdata1 = 10;
static int gdata1 = 0;
static int gdata3;int main() {    int a = 12;int b = 0;int c;static int e = 13;static int f = 0;static int g;return 0;
}

gdata1gdata4被初始化并且初始值不为零,被放在.data段。

gdata2gdata3gdata5gdata6未初始化或初始值为0,被放在.bss段。

至于abc他们并不产生符号,而是产生指令,比如说int a = 12;在x86指令集中为mov dword ptr[a], 0Ch。所以他们三个局部变量最终产生的是指令,被放在.text段。

然后关于efg这三个为静态局部变量,也是放在数据段(.data或.bss)的,但程序运行的时候是不会初始化的,只有第一次运行到他们才会进行初始化,分别放在数据段的.data.bss.bss

如果我们有如下操作:

//打印c
cout << c << endl;
//打印g
cout << g << endl;

打印c肯定不为0!因为它是栈上的无效值,但是如果打印g,肯定是0!因为他在.bss段。


综上所述,我们的红色部分都存储在.text部分,因为他们都会产生指令

但是我们一定要问自己一个问题,我们a、b、c已知那些数据都是放在栈上面的,有为什么说他们产生了指令呢

重点问题

为什么局部变量一会儿说在栈上,一会儿又是在 .text段

a, b, c编译后产生的指令是要放到.text段的,但是这个函数运行的时候,系统会在栈上面给该函数开辟一个栈帧,指令mov dword ptr[a], 0Ch就是把12放在a这块内存的4字节内存中,所以指令运行的时候会在栈空间上划分一块4字节的空间来存放12。也就是说a这个语句生成的时机是在函数运行时的,我们执行可执行文件后,先加载它的指令放在.text段,然后等到这条指令运行时,才会在栈空间开辟一个4字节的空间。

每一个进程的用户空间是私有的,但是内核空间是共享的!!!

如果我创建多个进程,QQ、酷狗音乐、VS。各自都有各自的用户空间,但是内核空间是共享的。

进程跟进程之间通信比较难的原因就是因为他们的用户空间是隔离的,谁也访问不到谁,但是内核空间是共享的。所以说进程之间的通信方式有哪些??

这样我们很容易理解了,进程间通信其实就是在内核空间划分了一块儿内存,这样一来进程1往内核共享的这块内存中写数据,进程2、3就都能看的见。
匿名管道通信

本模块推荐书籍:
《深入理解计算机系统》 尤其第七章 链接
《程序员的自我修养》尤其是

函数调用栈

给定一下代码:

int sum (int a, int b) {int tmp = 0;tmp = a + b;return tmp;
}
int main() {int a = 10;int b = 20;int ret = sum(a, b);cout << "ret: " << ret << endl;    return 0;
}

问两个问题:

  • 问题一:main函数调用sum,sum执行完之后,怎么知道回到哪个函数中
  • 问题二:sum函数执行完,回到main以后,怎么知道从哪一行指令继续运行

接下来我们会以此为例,讲解函数调用栈的使用过程

代码运行过程

函数运行时,要在栈帧上开辟空间。描述一个栈结构有栈顶和栈底就可以了,所以在这里我们给这个main函数的栈帧表示出来。

(1)ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。
(2)EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。

在这里,esp存储的是main函数栈帧栈顶的地址,所以说esp是可变的,随着栈的生产而逐渐变小。
ebp存放的是min函数栈帧栈底的地址,假如说栈底地址是0x0018ff40,ebp。
栈底是高地址,栈顶是低地址。

int a = 10;

汇编指令:mov dword ptr[a], 0Ah(真正的汇编指令是mov dword ptr[ebp-4], 0Ah,这里为了方便理解直接用了a)

我们来看,这个main函数的第一行代码是int a = 10;,执行的时候大家都知道a不产生符号,如果是汇编指令的话就是mov dword ptr[ebp-4], 0Ah,a是我们函数的第一个局部变量,所以他就出现在栈底,那为什么要用ebp减4呢,因为ebp是高地址,往上是低地址。
操作系统访问局部变量就是用栈底指针的偏移来访问

int b = 20

汇编指令:mov dword ptr[b], 14h (ptr[ebp-8])
图上画出来如图:

int ret = sum(a, b)

关于本条指令,ret是借助sum的返回值才完成初始化,所以我们先放到这里。

现在我们要开始调用函数了:一个函数的调用要先从右向左压参数,压栈往哪里压呢?往栈顶压!

  • 先压b,这块内存就是sum函数形参变量b的内存。所以形参内存开辟是由调用方函数来完成的。
  • 由于压栈操作push指令,所以esp也指向了栈顶。
  • 以上两个操作的汇编指令有两个,并且a也同理,所以一共四个汇编指令。完成a,b的压栈和相关指令如下:
mov eax, dword ptr[ebp-8]
push eax
mov eax, dword ptr[ebp-4]
push eax
  • 两个变量全部压完栈后,接下来就是函数调用指令call sum
    这个call指令会做两件事情,我们先展示call后面的汇编:
add esp, 8
move dword ptr[ebp-0Ch], eax

假设第一行指令的地址为08124458,call会把这个指令的地址入栈,因为我们后续等sum函数运行完,必须知道再继续运行哪一块代码。在这里我们就回答了上述的第二个问题
此时我们的内存情况如图:

  • 接下来我们要进入sum函数了
    其实我们需要首先执行我们的左括号,它对应三条指令,
push ebp
mov ebp, esp
sub esp, 4Ch

我们的push ebp会把ebp的地址压栈,还记得ebp是啥吗?没错,就是我们用来表示main函数的“栈帧”基地值,至此,main函数“栈帧”保护工作完成!这里也就回答了我们提出的第一个问题

紧接着mov ebp, esp,更新“栈帧”基准线,让他与栈顶平齐!现在他俩的地址相等了

再然后sub esp, 4Ch,也就是说我们的esp要往上走4Ch的空间,也就是给我们的sum函数开辟栈帧空间,主要是为了给我们的临时变量分配“栈”内存。

  • 接下来轮到我们sum函数中间的代码了,首先是int temp = 0;汇编指令如下:
mov dword ptr[ebp-4], 0

(这里的栈帧初始化只有windows的编译器才会做)

  • 接下来是temp = a + b,我们应该怎么取a和b呢?
    还记得之前我们的形参变量存到哪了吗?
    10=>int a 20=>int b这个位置。这里需要我们借助ebp来进行间接寻址。
mov eax, dword ptr[ebp+0Ch]
add ecx, dword ptr[ebp+8]		//这里计算a+b
mov dword ptr[ebp-4], eax		//把a+b的结果放到局部变量temp
  • 然后是return temp,注意temp是函数的局部变量,它是出不去的,temp是4个字节,返回他的时候不产生临时变量,而是直接通过eax寄存器带出去,所以汇编如下:
mov eax, dword ptr[ebp-4]
  • 最后到右括号了,我们先看汇编
mov esp, ebp
pop ebp
ret 0

第一行指令把ebp的值赋给esp,所以esp直接从上面跑到了sum函数栈帧的栈底,这里就是我们的回退栈空间

现在再看这段代码还安全吗?

int* func() {int data = 10;return &data;
}

我们的esp回退后,栈空间已经交还给系统了,这个地址返回之后还能用吗?肯定是不能,我们已经失去了对它的控制,成为了野指针。

第二行指令pop ebp,出栈,并把出栈元素的值赋给ebp,现在我们的栈顶放的是0x0018ff40,把它给ebp!我们的ebp又回到main函数栈帧的栈底了!

并且随着出栈,esp也往下走了,所以指向`0x08124458`

第三行指令ret ,也就是出栈操作,把出栈的内容放入CPU的PC寄存器(该寄存器存放下一行要执行的指令)中,我们现在出栈的是0x08124458,这个地址是什么还记得吗,就是我们的main函数中,call sum指令后面的add esp, 8这个指令的地址!

现在正式回到main函数调用完sum之后的指令位置了!

回到main函数

call sum
add esp, 8      //0x08124458
mov dword ptr[ebp-0Ch], eax

最后这两个指令就是完成ret的赋值操作。结束!

请回答本节开头的两个问题

看文本节后,能回答出这两个问题吗?

int sum (int a, int b) {int tmp = 0;tmp = a + b;return tmp;
}
int main() {int a = 10;int b = 20;int ret = sum(a, b);cout << "ret: " << ret << endl;    return 0;
}

回答两个问题:

  • 问题一:main函数调用sum,sum执行完之后,怎么知道回到哪个函数中
  • 问题二:sum函数执行完,回到main以后,怎么知道从哪一行指令继续运行

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

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

相关文章

ros 学习记录(二)URDF小车运动控制

URDF小车运动控制 准备工作创建 robot_xacro.launch 接上文&#xff0c;想用键盘控制小车在Gazebo中移动。 准备工作 名称版本ROSNoeticGazebo11.11.0 创建 robot_xacro.launch 通过运行这个launch文件&#xff0c;可以启动Gazebo仿真环境&#xff0c;并在仿真环境中加载和…

【八十五】【算法分析与设计】单调栈的全新版本,两个循环维护左小于和右小于信息,84. 柱状图中最大的矩形,85. 最大矩形

84. 柱状图中最大的矩形 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 示例 1: 输入&#xff1a;heights [2,1,5,6,2,3] 输出&#xff1a;10 解释&am…

nginx开启局域网https访问

需求 调试webRTC 功能,需要在局域网搭建https发给我协议; 实现 环境 局域网已部署有nginx; 部署可参考专栏文章 已安装Openssl 未安装可以执行 sudo yum install openssl 这个命令进行安装 生成证书和私钥 生成私钥:打开命令提示符或终端窗口,执行以下命令以生成私钥文件…

日本站群服务器的优点以及适合该服务器的业务类型?

日本站群服务器的优点以及适合该服务器的业务类型? 日本站群服务器是指位于日本地区的多个网站共享同一台服务器的架构。这种服务器架构有着诸多优点&#xff0c;使其成为许多企业和网站管理员的首选。以下是日本站群服务器的优点以及适合该服务器的业务类型的分析&#xff1…

企业怎样进行项目管理?

在当今快节奏的商业环境中&#xff0c;企业要想保持竞争力&#xff0c;有效的项目管理是关键。项目管理不仅涉及到规划、执行和监控&#xff0c;还包括团队协作、资源分配和风险管理等多个方面。zz-plan 作为一款集多种功能于一体的在线甘特图协作软件&#xff0c;为企业项目管…

【小笔记】问答系统可视化实现的三种方式

下面三种方式都是基于Python的哈&#xff0c;从简单到复杂。 方式一&#xff1a;命令行交互问答 优点&#xff1a;原始简单直接 方式二&#xff1a;使用Python可视化框架 优点&#xff1a;无需学习前端技术栈即可搭建一个web。 streamlit&#xff1a;⭐️⭐️⭐️⭐️gra…

MySQL——变量的定义与使用

新建链接&#xff0c;自带world数据库&#xff0c;里面自带city表格。 DQL # MySQL变量的定义与使用 #1、不允许数字作为开头 #2、只能用_或$符号&#xff0c;不允许使用其他符号 #3、不允许使用关键字或保留字 set userName小可爱; select userName; #标识符只影响当前查询#…

Web地理空间引擎

Web地理空间引擎是指用于在Web上创建和显示地理空间信息的软件平台。它们通常提供一组API和工具&#xff0c;用于加载、可视化和分析地理空间数据。Web地理空间引擎被广泛应用于各种应用&#xff0c;例如地图、导航、位置服务、游戏和模拟等。北京木奇移动技术有限公司&#xf…

24数维杯ABC题思路已更新!!!!

24数维杯A题保姆级思路&#xff0b;配套代码&#xff0b;后续参考论文 简单麦麦https://www.jdmm.cc/file/2710639/ 24数维杯B题保姆级思路&#xff0b;可执行代码&#xff0b;后续参考论文 简单麦麦https://www.jdmm.cc/file/2710640/ 24数维杯C题保姆级思路&#xff0b;可执…

四选一多路选择器

描述 制作一个四选一的多路选择器&#xff0c;要求输出定义上为线网类型。状态转移&#xff1a; d0 11 d1 10 d2 01 d3 00 信号示意图如下所示&#xff1a; 波形示意图&#xff1a; 输入描述&#xff1a; 输入信号 d1,d2,d3,d4 sel 类型 wire 输出描述…

宝塔面板如何删除一个站点

我们一般的网站都是PHPMySQL开发的&#xff0c;所以删除站点&#xff0c;就要先删数据库&#xff0c;再删网站目录 注意&#xff1a;一点要确保无用的再删 删除站点目录

01 JVM -- JVM 体系结构、HotSpot

1. JVM、HotSpot、 OpenJDK 的区别 JVM (Java Virtual Machine) 是一个虚拟机HotSpot 是 JVM 规范的一个实现。HotSpot 虚拟机通过即时编译 (JIT) 技术将 Java 字节码转换为本地机器码&#xff0c;以提高程序的执行效率。OpenJDK 是一个项目名&#xff0c;它在 HotSpot 的基础…

DRF之视图集

【 一 】视图集 ​ 在 RESTful 架构中&#xff0c;对资源的常规操作无非就是查询、新增、修改、删除等这么几种。为此&#xff0c;django-rest-framework 分别提供了对应通用类视图函数。但是&#xff0c;如果对同一个资源的不同操作逻辑分散在各个视图函数中&#xff0c;从逻…

C++STL细节,底层实现,面试题04

文章目录 19. STL19.1. 序列容器19.1.1. vector19.1.1.1. 底层实现和特点19.1.1.2. 常用函数19.1.1.3. emplace_back() vs push_back() 19.1.2. array19.1.2.1. 底层实现和特点19.1.2.2. 常用函数 19.1.3. deque19.1.3.1. 底层实现和特点19.1.3.2. 常用函数 19.1.4 list19.1.4.…

性能远超GPT-4!谷歌发布Med-Gemini医疗模型;李飞飞首次创业瞄准空间智能;疫苗巨头联合OpenAl助力AI医疗...

AI for Science 企业动态速览—— * 谷歌 Med-Gemini 医疗 AI 模型性能远超 GPT-4 * 斯坦福李飞飞首次创业瞄准「空间智能」 * 疫苗巨头 Moderna 与 OpenAl 达成合作 * 美国能源部推动 AI 在清洁能源领域的应用 * 美年健康荣获「2024福布斯中国人工智能创新场景应用企业TOP10」…

2024-5-9

今日流水账&#xff1a; 上午&#xff1a;又睡懒觉了&#xff0c;9点半才起来… 还是在调之前的那个 kernel pwn&#xff0c;但是 CONFIG_SLAB_FREELIST_HARDENED 加固泄漏 cookie 哪里我还是没搞明白&#xff08;&#xff1a;服了然后去理发、冲水卡了&#xff08;&#xff1a…

Centos 7.9如何使用源码编译安装curl最新版本

文章目录 1、前言2、curl源代码下载3、openssl安装4、编译curl4.1、配置编译环境4.2、编译输出二进制curl程序4.3、安装编译后的curl4.4、编译完成检查4.5、验证安装 1、前言 Centos 7.9&#xff0c;由于系统为2017年发行&#xff0c;且以稳定性为主&#xff0c;部分工具版本较…

吴恩达机器学习笔记:第 9 周-17大规模机器学习(Large Scale Machine Learning)17.3-17.4

目录 第 9 周 17、 大规模机器学习(Large Scale Machine Learning)17.3 小批量梯度下降17.4 随机梯度下降收敛 第 9 周 17、 大规模机器学习(Large Scale Machine Learning) 17.3 小批量梯度下降 小批量梯度下降算法是介于批量梯度下降算法和随机梯度下降算法之间的算法&…

Docker Desktop 修改容器的自启动设置

Docker Desktop 允许用户控制容器的自启动行为。如果你不希望某个容器在 Docker 启动时自动启动&#xff0c;你可以通过以下步骤来更改设置&#xff1a; 1. 打开 Docker Desktop 应用。 2. 点击右上角的设置&#xff08;Settings&#xff09;按钮&#xff0c;或者使用快捷键 Cm…

2024数维杯数学建模A题B题C题思路+模型+代码(开赛后第一时间更新)

2024数维杯数学建模A题B题C题思路模型代码&#xff08;开赛后第一时间更新&#xff09; https://mbd.pub/o/bread/ZpWakpdq https://mbd.pub/o/bread/ZpWakpdq 2024年第九届数维杯大学生数学建模挑战赛参赛规则 竞赛要求及论文提交方式; ①本次参赛作品统一在线提交到竞赛…