程序的机器级表示(三)

注:以下内容均来自开源学习组织DataWhale

程序的机器级表示(三)

1 过程

在大型软件的构建过程中,需要对复杂功能进行切分,过程提供了一种封装代码的 方式,它可以隐藏某个行为的具体实现,同时提供清晰简洁的接口定义。C语言中的函数和Java中的方法都是一种过程。

例如函数P调用函数Q,函数Q执行完毕返回函数P,这其中包含:

  • 传递控制
  • 传递数据
  • 分配和释放内存

2 运行时栈

在这里插入图片描述

栈为函数调用提供了后进先出的内存管理机制。

以函数P调用函数Q为例,当函数Q正在执行时,函数P及相关调用链上的函数都会被暂时挂起。

2.1 栈帧

当函数执行所需要的存储空间超过寄存器能存放的大小时,就会借助栈上的存储空间,这部分存储空间称为函数的栈帧。

对于函数P调用函数Q,栈帧包括:较早的帧、调用函数P的帧、正在执行函数Q的帧,如图所示:

在这里插入图片描述

其中,函数P调用函数Q时,会把返回地址(途中Return address)压入栈中,以便当函数Q执行结束后返回时继续从函数P的调用函数Q的位置继续执行函数P。

这个返回地址的压栈操作不是通过push指令完成的,而是由函数掉调用指令call实现的。

call指令不仅要将被调用函数得第一条指令得地址写到寄存器中,以实现函数调用,同时还要将返回地址(即被调用函数执行完毕后下一条指令的地址)压入栈中。

2.2 参数传递

如果一个函数的参数个数超过6个,超出部分就需要通过栈来传递。

参数1至参数6可以使用对应的寄存器如下:

在这里插入图片描述

例如有如下C代码:

void proc(long a1, long *a1p,
        int a2, long *a2p,
        short a3, long *a3p,
        char a4, long *a4p){
    a1p += a1;
    a2p += a2;
    a3p += a3;
    a4p += a4;
}

对应前6个参数通过寄存器来传递,而参数7和参数8都是通过栈来传递,如下示意:

a1 → \rightarrow %rdi a1p → \rightarrow %rsi

a2 → \rightarrow %edx a2p → \rightarrow %rcx

a3 → \rightarrow %r8w a3p → \rightarrow %r9

a4 → \rightarrow %rsp + 8

a4p → \rightarrow %rsp + 16

注:通过栈传递参数时,所有数据大小都是8字节的倍数,a4是char类型只占一个字节,但在栈上仍然分配了8个字节的存储空间。如图所示:在这里插入图片描述

注:同时寄存器的时使用有特殊的顺序规定,如下表,寄存器名字的使用取决于传递参数的大小,比如第一个参数大小是4字节,则需要用edi保存。

在这里插入图片描述

2.3 递归调用实例

有C代码如下:

long rfact(long n){
    long result;
    if(n <= 1){
        resutl = 1;
    }else{
        result = n * rfact(n - 1);
    }
    return rsult;
}

对应汇编代码如下:

rfact:
	pushq %rbx
	movq  %rdi, %rbx  // n在rdi中,这句话将n的值赋给rbx
	movl  $1, %eax  // 将1赋给eax
	cmpq  $1, %rdi // 比较1和n
	jle  .L35 // n = 1就跳到L35处执行
	leaq  -1(%rdi), %rdi // n - 1
	call  rfact // 递归调用
	imulq %rbx, %rax // n*(n - 1)
.L35
	popq  %rbx // 将上次保存的值弹出栈
	ret //返回上次调用处

假设n=3,第一次执行rfact时,由于需要使用rbx存储n的值,所以根据rbx的使用惯例,首先pushq保存rbx的值。当执行到

n=3,第一次执行到cmpq $1, %rdi时,不会跳到L35处,会继续执行leaq -1(%rdi), %rdi,然后call rfact再调用自身,再

pushq %rbx,这时压进栈的值是3,…,一直执行,直到n=1,会跳到L35处执行popq %rbx,这时2被弹出栈,rbx值恢复为2,ret返回到上次call处,继续执行imulq %rbx, %rax,此时rbx是2,rax是1,这句话执行完毕,rax为1*2=2,再顺序执行popq %rbx,rbx又被恢复为3,再ret,又是2*3,再次pop就恢复了rfact函数调用前的rbx的值,ret就结束了rfact。

可以看出,递归调用一个函数本身与调用其他函数是一样的,每次函数调用都有它自己私有的状态信息,栈分配与释放的规则与函数调用返回的顺序也是匹配的

3 数组

对于数组char A[8],每个元素占1个byte,假设其在内存中的首地址为 X a X_a Xa,则A[i]的地址为 X a + i X_{a+i} Xa+i,如下图所示:

在这里插入图片描述

而对于数组int B[4],每个元素占4个bytes,假设其在内存中的首地址为 X b X_b Xb,则B[i]的地址为 X b + 4 i X_{b+4i} Xb+4i,如下图所示:

在这里插入图片描述

指针运算

对于一个char类型指针char *p,和一个int类型指针int *q,对于指针p,q都进行加一操作后,p指向的内存地址向后移动1个位置,q向后移动4个位置,如下图所示:
在这里插入图片描述

嵌套数组

嵌套数组也称二维数组,例如数组int A[5][3],可看成5行3列的二维数组。在计算机中,二维数组在内存中按照“行优先”的顺序进行存储,即按行放入内存,如下图示意:

在这里插入图片描述

对于嵌套数组D的任意一个元素都可以通过下图中的计算公式来计算地址:

在这里插入图片描述

例:int A[5][3] & A [ i ] [ j ] = x A + 4 ( 3 i + j ) \&A[i][j]=x_A+4(3i+j) &A[i][j]=xA+4(3i+j)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值