深入了解C++:底层编译原理

简介: 深入了解C++:底层编译原理

进程的虚拟空间划分

任何编程语言,都会产生两样东西,指令和数据。

.exe程序运行的时候会从磁盘被加载到内存中,但是不能直接加载到物理内存中。Linux会给当前进程分配一块空间,比如x86 32位linux环境下会给进程分配2^32(4G)大小的空间,这个空间被叫做【进程的虚拟地址空间】,进程的虚拟地址空间其实并不存在,从底层来看它不过是内核创建的一系列数据结构而已。

以x86 32位linux为例,讲解进程的虚拟地址空间:这部分空间默认首地址是0x00000000,尾地址是0xFFFFFFFF,以0xCCCCCCCC为界被划分为两块,0x00000000~0xCCCCCCCC是【用户空间】,0xCCCCCCCC~0xFFFFFFFF是【内核空间】。

用户空间被进一步划分,0x00000000~0x08048000是不允许访问的空间。往后就是.【text段】,用于存储程序的机器代码(即已编译的指令),其中还有一部分是,rodata段(readonly data),用于存放只读数据(比如char *p=“hello”,所以不能修改)。

再往下就是.data段(专门存放初始化并且初始化不为0的数据),和.bss段(存放未初始化和初始化为0的数据),.bss段会自动把放入的数据做0初始化。最终生成的可执行文件就已经包含了./data段和./bss段里的数据了。

再往下就是.head段,俗称堆内存。

再往下就是共享库空间,用于加载.dll,.so等文件。

再往下就是stack段,俗称栈空间。栈是从高地址往低地址增长,堆是从低地址往高地址增长。栈空间在运行到相应的指令的时候才会被使用。

最后存放命令行参数和环境变量。

内核空间被划分为ZONE_DMA(16M左右),ZONE_NORMAL(800M左右,存放了各种控制块,比如tcb),ZONE_HIGHMEM(地址映射用)

注意:每一个进程用户空间是私有的,内核空间是共享的!所以进程之间通信需要通过内核空间。

#include<stdio.h>
//可执行文件展开的时候
//这些数据都被直接放入相应的./data或./bss段
int gdata1 = 0; //.data
int gdata2 = 0;//.bss
int gdata3;//.bss
static int gdata4 = 1; //.ata
static int gdata5 = 0;//.bss
static int gdata6;//.bss
int main(){
        //编译的时候产生的是指令,
        //可执行问价展开的时候放在.text中,
        //指令运行的时候才会再栈上开辟空间
        int a =12;
        int b =0;
        int c;
        //同.data或.bss
        static int c =13;
        static int f =0;
        static int g;
        return 0;
}

函数调用堆栈空间的过程

下面用一段很简单的代码说明栈堆调用过程。

#include <iostream>
using namespace std;
int sum(int a,int b)
{
  int temp = 0;
  temp = a + b;
  return temp;
}
int main()
{
  int a = 10;
  int b = 20;
  int ret = sum(a,b);
  cout << ret <<endl;
  return 0;
}

我们知道函数运行的时候需要在栈上开辟一块栈桢,.text里的汇编指令运行的时候,会往栈帧里写入相关数据。 比如int a = 10对应的指令运行的时候,会对main函数的栈帧进行压栈,从ebp(栈底)压入了a=10这个数据。

运行上述代码会依次发生以下事情:

1、ebp指针指向main栈帧的栈底,esp指针指向main栈帧的栈顶。esp和ebp指向的是当前调用的栈帧,能表示一块空间。

2、a、b、ret会依次从ebp压入main函数的栈帧中。

3、sum函数的实参会从右往左依次从esp(栈顶压入)。注意,栈帧的大小是会动态变化的,esp会保持位移一直指向栈帧的顶部。

4、把下一指令,也就是sum函数的指令的地址跟从实参地址一起从esp方向压入栈帧中。

5、把main函数调用的地址跟从sum函数调用的地址一起从esp方向压入栈帧中。

6、给sum函数开辟栈帧空间,ebp赋值成esp后,esp指向sum栈帧的顶部。sum函数栈帧可能会被初始化成0xCCCCCCCC。

7、给sum函数的栈帧空间从ebp压入temp。

8、计算a+b的值并赋值给temp空间。

9、回退栈帧,让ebp获取pop出的栈值,所以能重新指向main函数的栈底。

目录
相关文章
|
6月前
|
存储 自然语言处理 算法
【编译原理】LR(1)分析法:C/C++实现
【编译原理】LR(1)分析法:C/C++实现
768 0
|
6月前
|
存储 自然语言处理 算法
【编译原理】逆波兰式的产生及计算:C/C++实现
【编译原理】逆波兰式的产生及计算:C/C++实现
158 0
|
5月前
|
存储 编译器 C++
详细解读C++编译原理
详细解读C++编译原理
32 0
|
6月前
|
C++
深入了解C++:底层编译原理(二)
深入了解C++:底层编译原理(二)
29 2
|
6月前
|
存储 自然语言处理 编译器
【编译原理】词法分析:C/C++实现
【编译原理】词法分析:C/C++实现
230 1
|
自然语言处理 C++
编译原理 语法分析实验/课程设计(C++实现 附源程序下载)
编译原理 语法分析实验/课程设计(C++实现 附源程序下载)
编译原理 语法分析实验/课程设计(C++实现 附源程序下载)
|
自然语言处理 C++
编译原理 词法分析实验/课程设计C++实现
词法分析阶段是编译过程的第一个阶段,是编译的基础。这个阶段的任务是从左到右一个字符一个字符地读入源程序,即对构成源程序的字符流进行扫描然后根据构词规则识别单词(也称单词符号或符号)。词法分析程序实现这个任务
编译原理 词法分析实验/课程设计C++实现
|
6天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
29 4