为什么汇编不需要栈也能运行?

    我们知道C言语要运行的话是需要用到栈的,每次函数调用都会对栈进行生长和回退。本函数生长的栈用于本函数内数据存储,当本函数返回时需要回退栈,将栈恢复成调用者的现场继续运行。

    我们在aarch64体系结构上来观察一下C语言的这个调用和返回过程:

C言语代码(为了示范我们进行两级函数调用,main->swap1->swap):

#include <stdio.h>

static int swap(int *a1, int *a2)
{
	int tmp;

	if ((NULL == a1) || (NULL == a2))
	{
		return -1;
	}
	tmp = *a1;
	*a1 = *a2;
	*a2 = tmp;
	return 0;
}

static int swap1(int *a1, int *a2)
{
	int *p1 = a1;
	int *p2 = a2;
	int ret;
	
	ret = swap(p1, p2);
	return ret;
}

int main()
{
	int a = 1;
	int b = 2;
	int ret;

	ret = swap1(&a, &b);
	if (ret)
	{
		return -1;
	}
	printf("a = %d, b = %d\n", a, b);
	return 0;
}

将上述C代码通过gcc -S file.c -o file.s命令生成汇编代码。

	.arch armv8-a
	.file	"swap.c"
	.text
	.align	2
	.type	swap, %function
swap:
	sub	sp, sp, #32        //将sp下移32字节
	str	x0, [sp, 8]        //将第一个参数p1存到sp+8的地方
	str	x1, [sp]           //将第二个参数p2存到sp+0的地方
	ldr	x0, [sp, 8]        //将sp+8处所存的数据放到x0中,x0=p1
	cmp	x0, 0              //用p1和0比较
	beq	.L2                //如果p1和0相等则跳到L2处返回-1
	ldr	x0, [sp]           //x0=p1
	cmp	x0, 0              //用p2和0比较
	bne	.L3                //如果p2不等于0则跳到L3处执行交换数据代码,否则自然进入L2返回-1
.L2:
	mov	w0, -1
	b	.L4
.L3:
	ldr	x0, [sp, 8]
	ldr	w0, [x0]           //这两句完成w0=*p1
	str	w0, [sp, 28]       //将*p1存到sp+28的地方,tmp=*p1
	ldr	x0, [sp]
	ldr	w1, [x0]           //这两句完成w1=*p2
	ldr	x0, [sp, 8]        // x0=p1
	str	w1, [x0]           // *p1=*p2
	ldr	x0, [sp]           // x0=p2
	ldr	w1, [sp, 28]       // w1=tmp
	str	w1, [x0]           // *p2=tmp
	mov	w0, 0              //执行成功将返回值置为0
.L4:
	add	sp, sp, 32         //恢复x29和lr的值,并将sp上移32个字节,销毁本次函数swap1使用的栈
	ret                    //返回
	.size	swap, .-swap
	.align	2
	.type	swap1, %function
swap1:
	stp	x29, x30, [sp, -64]!    //将本函数的栈底指针x29和返回地址lr保存到栈中,并将栈顶指针sp向下移64个字节,完成开辟本函数需要使用的栈
	add	x29, sp, 0              //更新栈底指针,此时x29 = sp
	str	x0, [x29, 24]           //将第一个参数a1存到sp+24的地方,占8个字节
	str	x1, [x29, 16]           //将第二个参数a2存到sp+16的地方,占8个字节
	ldr	x0, [x29, 24]           //将sp+24处所存的数据放到x0中
	str	x0, [x29, 56]           //将x0中的数据存入sp+56的地方,上面一句和这一句完成C代码中的p1=a1
	ldr	x0, [x29, 16]           //将sp+16处所存的数据放到x0中
	str	x0, [x29, 48]           //将x0中的数据存入sp+48的地方,完成C代码中的p1=a2
	ldr	x1, [x29, 48]           //准备调用下级函数的第二个参数x1,将sp+48处存的数据放到x1中,x1=p2
	ldr	x0, [x29, 56]           //准备调用下级函数的第一个参数x0,x0=p1
	bl	swap                    //调用swap函数,参数为p1和p2
	str	w0, [x29, 44]           //将返回值存入sp+44的地方
	ldr	w0, [x29, 44]           //将sp+44处存放的数据放到w0中作为返回值,这两句没有也可以,因为这两句之间w0的值没有人改过
	ldp	x29, x30, [sp], 64      //恢复x29和lr的值,并将sp上移64个字节,销毁本次函数swap1使用的栈
	ret                         //返回
	.size	swap1, .-swap1
	.section	.rodata
	.align	3
.LC0:
	.string	"a = %d, b = %d\n"
	.text
	.align	2
	.global	main
	.type	main, %function
main:
	stp	x29, x30, [sp, -32]!
	add	x29, sp, 0
	mov	w0, 1
	str	w0, [x29, 24]
	mov	w0, 2
	str	w0, [x29, 20]
	add	x1, x29, 20
	add	x0, x29, 24
	bl	swap1
	str	w0, [x29, 28]
	ldr	w0, [x29, 28]
	cmp	w0, 0
	beq	.L8
	mov	w0, -1
	b	.L10
.L8:
	ldr	w1, [x29, 24]
	ldr	w2, [x29, 20]
	adrp	x0, .LC0
	add	x0, x0, :lo12:.LC0
	bl	printf
	mov	w0, 0
.L10:
	ldp	x29, x30, [sp], 32
	ret
	.size	main, .-main
	.ident	"GCC: (Debian 6.3.0-18+deb9u1) 6.3.0 20170516"
	.section	.note.GNU-stack,"",@progbits

上面的汇编代码中对swap1和swap函数进行了注释,我们可以看到这两个函数都使用了栈对寄存器或者中间运算数据进行存储,那么下面我们将上述汇编进行修改一下,改为swap1和swap函数不使用栈的方式:

	.arch armv8-a
	.file	"swap.c"
	.text
	.align	2
	.type	swap, %function
swap:
	cmp	x0, 0
	beq	.L2         //第一个参数判空
	cmp	x0, 0
	bne	.L3         //第二个参数判空
.L2:
	mov	w0, -1
	b	.L4
.L3:
	ldr	w2, [x0]    // tmp1=*p1
	ldr	w3, [x1]    // tmp2=*p2
	str	w3, [x0]    // *p1 = tmp2
	str	w2, [x1]    // *p2 = tmp1
	mov	w0, 0       //完成值交换,置返回值为0
.L4:
	ret             //本函数中我们没有修改lr也没有修改上级函数用的x6所以可以不需要恢复任何现场直接返回
	.size	swap, .-swap
	.align	2
	.type	swap1, %function
swap1:
	mov	x6, x30          //只保存lr到x6寄存器中
	mov	x7, x0           //将第一个参数放到x7中
	mov	x8, x1           //将第二个参数放到x8中
	mov	x0, x7           //又将第一个参数从x7中放到x0中
	mov	x1, x8           //又将第二个参数从x8中放到x1中,其它这几句话没有意义,只是为了模仿上面的C代码,我们可以看到这几句话没有修改使用其它值修改x0和x1所以可以直接什么都不做,将参数透传给swap函数
	bl	swap             //调用swap函数,参数放在x0和x1中
	mov	x30, x6          //从x6中恢复lr,注意:我们在本级函数使用了x6保存数据,那么下级函数如果要使用x6则需要保存x6的现场和恢复x6后才能返回到本级函数。
	ret
	.size	swap1, .-swap1
	.section	.rodata
	.align	3
.LC0:
	.string	"a = %d, b = %d\n"
	.text
	.align	2
	.global	main
	.type	main, %function
main:
	stp	x29, x30, [sp, -32]!
	add	x29, sp, 0
	mov	w0, 1
	str	w0, [x29, 24]
	mov	w0, 2
	str	w0, [x29, 20]
	add	x1, x29, 20
	add	x0, x29, 24
	bl	swap1
	str	w0, [x29, 28]
	ldr	w0, [x29, 28]
	cmp	w0, 0
	beq	.L8
	mov	w0, -1
	b	.L10
.L8:
	ldr	w1, [x29, 24]
	ldr	w2, [x29, 20]
	adrp	x0, .LC0
	add	x0, x0, :lo12:.LC0
	bl	printf
	mov	w0, 0
.L10:
	ldp	x29, x30, [sp], 32
	ret
	.size	main, .-main
	.ident	"GCC: (Debian 6.3.0-18+deb9u1) 6.3.0 20170516"
	.section	.note.GNU-stack,"",@progbits

从修改后的汇编代码中我们可以看出,swap1和swap两个函数中没有使用栈,没有动过sp和x29寄存器,也同样完成了数据交换,达到了C代码想要完成的目的。怎么做到的呢?其实只是将之前需要保存到栈中的寄存器和数据保存到了另外的寄存器中,相当于是把寄存器当成了临时存储器来使用。所以不用栈汇编语言也可以运行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值