虚拟地址空间和函数调用过程

一、虚拟地址空间

在这里插入图片描述
.text:代码段,存放代码,指令
.rodata:只读数据区,存放常量
.data:存放初始化且初始化值不为0的数据
.bss:存放未初始化和初始化为0的数据(包括全局变量,static修饰的变量)
.heap:堆区
.stack:栈区(可存放函数形参和局部变量)

变量生存期

把程序运行时一个变量占有内存空间的时间段称为该变量的 生存期
C++把变量的生存期分为: 静态、自动和动态 三种。

静态生存期: 全局变量都具有静态生存期,它们的内存空间程序开始执行时就进行分配,直到程序结束才被收回。

自动生存期: 局部变量和函数形参一般都具有自动生存期,它们的内存空间在程序执行到定义它们的复合语句(包括函数体)时才分配,当定义它们的复合语句执行结束时内存被收回。

动态生存期: 具有动态生存期的变量的生存时间是由程序员自己控制的,其内存空间用new操作符分配,用delete回收。

在定义局部变量时,可以为它们加上存储类修饰符 auto、static 和 register 来指出它们的生存期。

定义为 static 存储类型的局部变量具有静态生存期,它们也被存放在静态数据区。

在这里插入图片描述

  1. Linux将每个进程的4GB的独立地址空间又划分为用户地址空间(0x00000000~0xBFFFFFFF)内核地址空间(0xC000000~0xFFFFFFFF) 两部分
  2. 操作系统内核代码和数据存放在内核地址空间,每个进程自己私有的代码和程序存放在用户地址空间
  3. 虽然linux的内核代码和数据被映射到了每个进程的地址空间中(所有进程看到的内容是相同的),但在物理内存中,只有内核代码和数据的一份拷贝
  4. 随着进程的切换,当前有效的用户地址空间也在切换
  5. CPU有一个CR3寄存器,指向当前运行进程的页表。

虚拟内存管理的作用:

  1. 为每个进程提供独立的内存空间
  2. 通过后台在物理内存和外存之间的数据换入换出,给进程创造“内存很大”的假象
  3. 通过物理内存页与虚拟内存页的一对多映射实现共享内存、共享函数库(动态链接库)、共享操作系统内核空间
  4. 利用延迟分配提高内存利用率以及动态内存分配速度
  5. 利用缺页中断实现内存保护
  6. 利用写时拷贝技术提高进程创建效率
  7. 通过“文件—虚拟空间”映射机制提供灵活的文件访问方式
#include<stdio.h>

int g_data1 = 10;         // .data
int g_data2 = 0;          // .bss
int g_data3;              // .bss

static int g_data4 = 11;  // .data
static int g_data5 = 0;   // .bss
static int g_data6;       // .bss

int main() {
    // 生成汇编指令,而不是产生数据
    // 运行到该段代码时,才在函数栈帧上开辟4字节的空间存放数据
    int a = 10;       
    int b = 0;
    int c;

    // 放在数据段,程序启动的时候不会初始化,运行到该代码后再初始化
    // 程序运行起来后,.bss段被清0
    static int e = 10; // .data
    static int f = 0;  // .bss
    static int g;      // .bss
    return 0;
}

每一个进程的用户空间是私有的,内核空间是共享的
在这里插入图片描述

二、函数调用过程

  1. 参数入参
  2. 函数栈帧开辟
  3. 返回值返回
  4. 栈帧回退
  5. 参数清除

在这里插入图片描述

形参内存空间不在被调用函数的栈空间,而在调用函数的栈帧

小Tips: 函数的参数都在ebp所指示的内存地址的正偏移处,函数内部的局部变量都在ebp所指示的内存地址的负偏移处。此时ebp指向当前函数栈的栈底。

一、参数入参
int fun(int a, int b) {
	int c = a + b;
	return c;
}

int main() {
	int a = 10;
	int b = 20;
	int c = 0;
	c = fun(a, b);
	return 0;
}

在这里插入图片描述

入参过程
  1. push数据:栈顶寄存器 esp 向上移动(低地址方向增长),将push的数据放到栈顶
  2. 入栈顺序:参数从右向左
  3. 入栈方式:push寄存器到栈顶
理解入栈顺序代码
void fun1(int a, int b) {
	printf("a = %d, b = %d\n", a, b); // a = 1, b = 0
}

int main() {
	int i = 0;
	fun1(i++, i++); // 右边参数i=0入栈,i++,然后左边参数入栈
	printf("i = %d", i); // i = 2
	return 0;
}
8字节数据入栈
#include<stdio.h>

struct Node {
	int d1;
	int d2;
};

int fun(struct Node a, struct Node b) {
	return 0;
}

int main() {
	struct Node node1 = { 10, 20 };
	struct Node node2 = { 30, 40 };
	int c = fun(node1, node2);
	return 0;
}

在这里插入图片描述

struct Data
{
	int d1;
	int d2;
};

int fun(struct Data a, struct Data b)
{
	int c = 0;
	//c = a + b;
	return c;
}

int main()
{
	int c = 0;
	struct Data a = { 20,30 };
	struct Data b = { 40,50 };
	c = fun(a, b);
}

在这里插入图片描述
主函数:
在这里插入图片描述
在这里插入图片描述

子函数:
在这里插入图片描述

12字节数据入栈
#include<stdio.h>

struct Node {
	int d1;
	int d2;
	int d3;
};

int fun(struct Node a, struct Node b) {
	return 0;
}

int main() {
	struct Node node1 = { 10, 20, 30 };
	struct Node node2 = { 40, 50, 60 };
	int c = fun(node1, node2);
	return 0;
}

在这里插入图片描述

  1. 没有用push寄存器的方法,直接将栈顶指针向上偏移,然后将参数的值拷入
  2. 入参顺序:从右向左,结构体参数入栈时,从低地址数据到高地址数据入栈
二、函数栈帧开辟

在这里插入图片描述
返回4字节

#include<stdio.h>

int fun(int a, int b) {
	return 0;
}

int main() {
	int a = 10;
	int b = 20;
	int c = fun(a, b);
	return 0;
}

在这里插入图片描述

返回8字节

#include<stdio.h>

struct Node {
	int d1;
	int d2;
};

struct Node fun(int a, int b) {
	struct Node ret = { a, b };
	return ret;
}

int main() {
	int a = 10;
	int b = 20;
	struct Node node = fun(a, b);
	return 0;
}

在这里插入图片描述

返回12字节

struct Node {
	int d1;
	int d2;
	int d3;
};

struct Node fun(int a, int b) {
	struct Node d = { 10, 20, 30 };
	return d;
}

int main() {
	int a = 10;
	int b = 20;
	int c = 30;
	struct Node node = fun(a, b);
	return 0;
}

参数入参
在这里插入图片描述

参数a、b先入栈,然后入栈一个存放返回值的临时空间,入栈调用函数下一行的地址,保存ebp到调用函数栈帧之上,并且开辟栈帧(call指令

返回值

读取入参时候入栈的参数,放在调用函数的栈空间
在这里插入图片描述
eax指向调用函数栈空间内用于接收返回值的地址(也即上面的[ebp-108h]),此时的ebp则指向被调用函数的栈空间,用eaxebp把返回值写入调用函数的栈空间。

返回值

在这里插入图片描述
栈帧回退
在这里插入图片描述

  1. pop恢复寄存器
  2. esp=ebppop ebp 两步退出被调用方的栈帧
  3. ret:将原来入栈的调用方 下一条指令的地址出栈 并恢复

参数清除

在这里插入图片描述

int func_B(int arg_B1, int arg_B2){
    int var_B1, var_B2;
    return 0;
}

int func_A(int arg_A1, int arg_A2){
    int var_A1, var_A2;
    func_B(4, 3);
    return 0;
}

void main(){
    int var_main;
    func_A(2, 1);
}

在这里插入图片描述

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bugcoder-9905

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值