【C++语言】深入学习C++要修炼的内功

devtools/2024/10/23 20:26:20/

一、进程虚拟地址空间区域划分

我们先来分析以下代码:

int gdata1 = 10;
int gdata2 = 0;
int gdata3;static int gdata4 = 11;
static int gdata5 = 0;
static int gdata6;int main()
{int a = 12;int b = 0;int c;static int e = 14;static int f = 0;static int g;return 0;
}

       在任何编程语言中,编译一个可执行文件会产生两种东西:指令和数据,当我们需要执行一个程序的时候,我们需要将这个程序从磁盘加载到内存中,不可能直接加载到物理内存中的。接下来,我们在 X86 32位Linux环境下进行讨论。

       在每一个进程的用户空间是私有的,但是内核空间是共享的,在Linux地址内存空间时,我们会发现每一个进程都有一个用户页表和内核页表,内核页表是共享的,但是用户页表不是共享的。 

1.1 分析一下虚拟内存和虚拟地址空间??

虚拟内存和虚拟地址空间是计算机系统中与内存管理相关的的概念,他们紧密相关但有所不同:

1.1.1 虚拟内存:

  • 虚拟内存是一种内存管理技术,他允许计算机使用比物理内存(RAM)更多的内存空间,他通过将部分内存存储在硬盘上的交换空间(swap space)或者页面文件(page file)来实现。
  • 虚拟内存使得应用程序可以拥有比实际物理内存更大的地址空间,操作系统会在需要时将数据从虚拟内存交换到物理内存中。
  • 虚拟内存的主要目的是扩展可用的内存空间,支持更多的应用程序同时运行,并提高内存使用的效率。

1.1.2 虚拟地址空间:

  • 虚拟地址空间是操作系统为每一个进程提供的一段唯一的地址范围,这个地址范围通常远大于物理内存的大小。
  • 每一个进程都认为自己独占整个内存空间,但是实际上,这个空间是由操作系统通过映射技术在物理内存中实现的。
  • 虚拟地址空间允许多个进程同时运行但是不会相互干扰,因为每一个进程都有自己的地址空间。
  • 虚拟地址空间通常被分为多个段(比如代码段,数据段,堆,栈等),每一个段都有自己的权限和特性。

1.1.3 区别

  • 概念层面:虚拟内存是一种技术或机制,而虚拟地址空间是操作系统为进程提供的一种资源。
  • 功能层面:虚拟内存主要用于扩展内存容量,而虚拟地址空间主要用于隔离和保护进程的内存。
  • 实现层面:虚拟内存涉及到内存的交换和页面替换算法,而虚拟地址空间涉及到地址转换和内存保护机制。

       虚拟内存的实现通常依赖于虚拟地址空间。操作系统通过将虚拟地址空间映射到物理内存,使得进程可以访问超出物理内存限制的数据。这种映射通常是通过内存管理单元(MMU)来完成的,MMU负责将虚拟地址转换为物理地址。

1.2 ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM分别是什么??

  ZONE_DMAZONE_NORMALZONE_HIGHMEM是Linux内核中用于区分不同内存区域的术语,他们是Linux内存管理的一部分,特别是在物理内存的布局和分配方面。这些区域的划分有助于内核根据不同的硬件特性和内存访问需求来优化内存的使用,以下是每一个区域的简要说明:

  1. ZONE_DMA

    • 这个区域包含了最低的16MB的物理内存(0MB到16MB)。在传统的 PC 架构中,直接内存访问(DMA)控制器只能访问这16MB的内存空间。
    • 由于 DMA 控制器的限制,这个区域的内存不能用于执行代码,因为它可能会被 DMA 操作覆盖。通常用于存放需要 DMA 访问的数据缓冲区。
  2. ZONE_NORMAL

    • 这个区域通常包含了从16MB到896MB的物理内存(这个范围可能会根据系统的具体配置有所不同)。
    • 这个区域的内存可以用于执行代码,存放数据,以及分配给进程使用。
    • 在许多系统中,ZONE_NORMAL 是最常见的内存区域,用于大多数常规的内存分配。
  3. ZONE_HIGHMEM

    • 这个区域包含了高于896MB的物理内存。在早期的 PC 架构中,高于1GB的内存(即高于ZONE_NORMAL的上限)被称为高端内存(high memory),因为它超出了某些处理器直接寻址的能力。
    • 高端内存区域的内存页不能被直接映射到处理器的地址空间中,因此访问这些页需要使用特定的内存管理技术,如内核同页(kswapd)或页面分配器(page allocator)。

       在现代的 PC 架构中,随着技术的发展,这些区域的划分可能已经不那么严格,特别是在支持物理地址扩展(PAE)的64位系统中,处理器可以直接访问更多的内存,从而减少了对这些区域划分的需求。然而,这些术语和概念在内核的内存管理中仍然有其历史意义和应用。(这里看的优点难受)!!!!!!!!!!!

二、从指令角度掌握函数调用堆栈详细过程

       在这个问题下,我们通过一段简单的相加函数来进行一下函数堆栈的调用过程。函数栈帧是我们需要先了解的东西:

2.1 前置概念:

2.1.1 什么是函数栈帧

       函数栈帧是在内存中的栈区为被调函数开辟的一块空间,里面存放的是该函数中定义的变量等东西,当函数运行完毕栈帧将会被销毁。我们在来复习一下栈这个概念,栈实际上就是一种数据结构,他是一种先进后出的数据表,栈常见的操作有两种:

  • Push:入栈,为栈增加一个元素
  • Pop:出栈,从栈中取出栈顶

2.1.2 寄存器

  • eax:是"累加器"(accumulator), 用来存放函数的返回值。
  • ebx:是"基地址"(base)寄存器,可作为储存器指针来使用, 在内存寻址时存放基地址。
  • ecx: 是计数器(counter), 在循环和指针操作时,要用它来控制循环次数。
  • edx:是"数据寄存器’,在进行乘、除法运算时,可作为默认的操作数参数参与运算。
  • ebp和esp:他俩都是指针寄存器它最经常被用作高级语言函数调用的"框架指针"(frame pointer),简单来说这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的。
  • ebp:存放栈底的地址(指向栈底)
  • esp:存放栈顶的地址(指向栈顶)
  • edi和esi:它俩都是变址寄存器,常用来配合使用完成数据的赋值操作

2.1.3 汇编指令

  • move:move A,B (将数据B移到数据A)
  • push:压栈(入栈)
  • pop:出栈
  • call:调用函数
  • add:加法
  • sub:减法
  • rep: 重复
  • lea:加载有效地址

2.1.4 每一个函数调用都需要创建函数栈帧

       所有的函数调用都会在内存里面的栈区创建函数栈帧,包括main函数。通过上面对函数栈帧的介绍我们可以知道,函数栈帧是为被调函数在内存的栈区中开辟的一块空间,所以这里间接证明了main函数也是被调函数。

2.2 函数栈帧的创建和销毁详解

2.2.1 main函数也是被调用的

       我们在进行调试中,在界面中找到调用堆栈,我们可以直观的看出main函数是被调用的。main函数是被 __tmainCRTStartup 这个函数调用的。

2.2.2 main函数的函数栈帧的创建和销毁

       由于main函数是被其他函数所调用,所以在 __tmainCRTStartup 这个函数调用main函数的时候会为main函数在内存的栈区中开辟空间:

栈区的使用习惯是先试用高地址在使用低地址,在顶上放数据。 

第一步是push ebp

       通过上图可以看出第一步是push ebp,这是因为mian函数是被__tmainCRTStartup 这个函数调用的,在调用main函数之前,esp和ebp分别指向__tmainCRTStartup 函数的栈顶和栈底,当调用main函数的时候,就要为main函数开辟相应的函数栈帧,此时esp和ebp就需要移动去指向main函数的函数栈帧。

       push ebp就是把__mainCRTStartup 函数栈底的地址压栈,ebp的值压入后,esp指针会上移一位

第二步:move ebp,esp

move ebp,esp的意思是:把esp的值给ebp。
esp当前的值是0x0037fdb4,也就是说esp此时指向0x0037fdb4这个地址,把esp的值给ebp后,ebp也指向0x0037fdb4这个地址

第三步:sub esp,0E4h(开辟函数栈帧)

sub esp,0ECh,就是给esp减去一个0E4h。这里的0E4h是一个十六进制的数字(h表示是十六进制),0E4对应的10进制数字就是228。这也就意味着esp指向的地址会减小228,对应图示就是esp指针会上移228个字节

第四步:3个push

第五步:lea加载有效地址

       lea是load effective address(加载有效地址)的缩写。而 lea edi,[ebp-0E4h]的意思就是把ebp-0E4h这个地址放到edi里面。还记得第二步move ebp,esp嘛?。执行完第二步后ebp和esp指向了同一个地址,然后第三步sub esp,0E4h,让esp指向的地址减了0E4h(228),,此后ebp指向的地址没有发生任何变化,第四步的3个push操作让esp指向的地址又减小了12(一次push减小4,3次push就减小12)。而当前的第五步中的地址ebp-0E4h也就是在执行完第三步后esp所指向的地址,就是要把这个地址放到edi里面(其实就是让edi指向这个地址,因为edi是一个变址寄存器,用存储地址)

函数栈帧 

三、从编译器角度理解C++代码的编译和链接原理

3.1 程序的翻译环境和执行环境

       在国际C中的任何一种实现中,存在两个不同的环境。第一种是翻译环境,在这个环境中源代码被转换成为可执行的机器指令;第二种是执行环境,他用于实际执行代码。

3.1.1 翻译环境

       源文件会经过编译器的处理转换为目标文件,再进入链接器和链接库组成可执行程序。链接库是编译器提供的一些东西。编译器在windows上使用的c1.exe,链接器使用的是link.exe。在everything中可以查看到这两个程序。

3.1.2 编译 + 链接

在编译阶段中有三个部分(如下图所示):

在编译完成后,是需要进行链接的:1.合并段表;2.符号表的合并和重定位。

       我们为了在链接中跨文件找到函数,需要将elf格式中相同的数据进行合并,将符号表进行合并。每一个符号表中都有相对应的地址,如果只是声明,则存放的地址不能使用。

       我们需要使用符号表去查找函数是通过地址去查找的,如果表中的地址是错误的,则无法找到相对应的函数,出现链接错误。

       但是在C++中有函数重载,对于重命名是复杂的,因为参数不一样,重命名也不一样。C++中的函数命名规则中,需要将函数名和函数参数进行结合,所以可以进行函数重载,但是在C语言中,由于函数命名规则是只和函数名有关,所以C语言中没有函数重载。

3.1.3 运行环境

程序执行的过程:
  1. 程序必须载入内存中,在有操作系统的环境中,一般由操作系统完成,在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存中来完成。
  2. 程序的执行便开始了,接着调用main函数
  3. 开始执行程序代码,这个时候程序将使用一个运行时堆栈,存储函数的局部变量和返回地址,程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序整个执行过程一直保留他们的值
  4. 终止程序,正常终止main函数,也可能是意外终止。

       我们的程序需要经过预编译,编译,汇编的步骤后,会生成二进制可重定位的目标文件(*.obj),这是编译的过程,接下来是链接的过程,编译完成的所有.o文件和静态库文件。

  • 步骤一:所有.o文件段的合并,符号表合并之后,进行符号解析;所有对符号的引用,都要找到该符号定义的位置;
  • 步骤二:(链接的核心)符号的重定位(重定向)生成 xxx.exe、a.out。

*.o 文件的组成格式:

预编译:#开头的命令需要在预编译中进行处理,#pragma 不在预编译中处理,#pragma lib 需要进行连接库,所以这条指令必须存活在链接阶段的。

编译:gcc  g++ -O_   编译过程中,符号似乎不分配虚拟地址的,但是指令需要进行生成,所以指令上指向的地址是0,

汇编:符号表的输出,符号怎么理解??符号什么时候分配虚拟地址空间?

可执行文件的组成格式:

xxx.exe    a.out   -o 可执行文件的名称

objdump 指令

我们可以通过objdump指令来查代码的符号表,发现普通变量是全局的,但是静态变量不是全局的,只能在自己的文件中查看。

readelf -h  xxx.o 指令

readelf -S xxx.o 指令

objdump -S xxx.o 指令

objdump -t xxx.o 指令


http://www.ppmy.cn/devtools/128239.html

相关文章

unity学习笔记-安装与部署

unity学习笔记-安装与部署 unity & visual studio下载unityvisual studio 创建工程项目内的布局介绍初始化项目各目录介绍1. 场景视图(Scene)2. 游戏视图(Game)3. 层次结构视图(Hierarchy)4. 检查器视图…

#每日一题#自动化 2024年10月

#每日一题#自动化 2024年10月 1、深拷贝和浅拷贝的区别是什么? 参考答案: 深拷贝是将对象本身复制给另一个对象。这意味着如果对对象的副本进行更改时不会影响原对象。在 Python 中,我们使用 deepcopy()函数进行深拷贝…

rootless模式下测试istio Ambient功能

前置需求 rootless k8s测试环境搭建:https://blog.csdn.net/longtds/article/details/142916697 istio Ambient istio安装 通过加速下载istio release包,解压并安装为ambient模式 wget https://mirror.ghproxy.com/https://github.com/istio/istio/r…

ESP32-S3学习笔记:分区表(Partition Table)的二进制分析

目录 一、参考资料 二、准备工作 三、开始分析 一、参考资料 用于研究的官方示例代码:esp-idf-v5.3\examples\storage\partition_api\partition_find参考的官方文档:ESP-IDF编程指南:分区表 二、准备工作 用VS Code打开示例代码&#xf…

Java最全面试题->Java基础面试题->JavaSE面试题->面向对象面试题

文章目录 面向对象1.面向对象和面向过程的区别2.面向对象有哪些特性3.多态的实现机制4.Java语言有哪些特点5.JDK、JRE、JVM三者的联系和区别 面向对象 下边是我自己整理的面试题,基本已经很全面了,想要的可以私信我,我会不定期去更新思维导图…

C++中的vector使用与实现

一、vector的使用 1.1 vector的定义 是一种类模板 template < class T, class Alloc allocator<T> > class vector; 其中的模板参数Alloc是在使用空间配置器&#xff08;内存池&#xff09;&#xff0c;并给了缺省值&#xff0c;暂时不深究 1.2遍历方式 1.…

12、论文阅读:SpikeYOLO:高性能低能耗目标检测网络

SpikeYOLO:高性能低能耗目标检测网络 前言解释介绍相关工作论文提出的方法网络输入SpikeYOLO架构概述网络输出宏观设计微观设计I-LIF脉冲神经元LIFI-LIF实验代码前言 脉冲神经网络(Spiking Neural Networks, SNNs)具有生物合理性和低功耗的优势,相较于人工神经网络(Artif…

毕设项目分享 深度学习动物识别系统(源码+论文)

文章目录 0 前言1 项目运行效果1 背景2 算法原理2.1 动物识别方法概况2.2 常用的网络模型2.2.1 B-CNN2.2.2 SSD 3 SSD动物目标检测流程4 实现效果5 部分相关代码5.1 数据预处理5.2 构建卷积神经网络5.3 tensorflow计算图可视化5.4 网络模型训练5.5 对猫狗图像进行2分类 6 最后 …