汇编层面来理解C语言的值传递和引用传递的区别

写在前面:突然心血来潮,想给读者写一篇关于汇编层面来理解值传递和引用传递的区别的帖子。

版本信息:Linux系统(centos7)、x86汇编、32位系统、gcc 4.8.5.

c语言代码如下:

add_value方法:传入2个int变量(值)。

add_reference方法:传入2个int指针(地址)。

#include<stdio.h>

int add_value(int a,int b){
  int c = a;
  int d = b;
  return c + d;
};
int add_reference(int *a,int *b){
  int *c = a;
  int *d = b;
  return (*c) + (*d);
}

int main(){
  int a = 1;
  int b = 2;
  add_value(a,b);
  add_reference(&a,&b);
  return 1;
}

通过gcc命令编译c语言,生成对应的汇编代码。

gcc -S -m32 demo.c

汇编如下:

main方法汇编:

main:
	pushl	%ebp                # 把ebp寄存器存放的地址压栈,目的为了返回         
	movl	%esp, %ebp          # 让esp和ebo保持在一个位置。
	subl	$24, %esp           # 开辟24个字节大小,也就是esp的地址往底地址推24个地址。
	movl	$1, -4(%ebp)        # 把立即数1放入到ebp-4的位置(放入栈中)
	movl	$2, -8(%ebp)        # 把立即数2放入到ebp-8的位置(放入栈中)
	movl	-8(%ebp), %edx      # 把ebp-8位置的值放入到edx寄存器中(目的是为了传递值)
	movl	-4(%ebp), %eax      # 把ebp-4位置的值放入到eax寄存器中(目的是为了传递值)
	movl	%edx, 4(%esp)       # 把edx寄存器的值放入到esp-4的位置(目的是通过栈传参给方法)
	movl	%eax, (%esp)        # 把eax寄存器的值放入到esp的位置(目的是通过栈传参给方法)
	call	add_value           # 调用add_value。
	leal	-8(%ebp), %eax      # 把ebp-8的地址放入到eax寄存器中(注意这里放入的是地址)
	movl	%eax, 4(%esp)       # 把eax寄存器存放的地址放入到esp+4的位置中(目的是通过栈传参给方法)
	leal	-4(%ebp), %eax      # 把ebp-4的地址放入到eax寄存器中(注意这里放入的是地址)
	movl	%eax, (%esp)        # 把eax寄存器存放的地址放入到esp的位置中(目的是通过栈传参给方法)
	call	add_reference       # 调用add_reference
	movl	$1, %eax
	leave
	ret

lea指令:获取到地址,移动地址。

call指令:调用方法

add_value方法汇编:

add_value:
	pushl	%ebp                # 将上个栈帧的ebp地址压入当前栈帧(为了返回)
	movl	%esp, %ebp          # 将esp和ebp保持在同一个位置
    subl	$16, %esp           # 在栈上开辟16个字节大小空间。
	movl	8(%ebp), %eax       # 把ebp+8地址的值移动到eax寄存器中(栈传值)
	movl	%eax, -4(%ebp)      # 把eax寄存器的值放入到ebp-4的位置。
	movl	12(%ebp), %eax      # 把ebp+12地址的值移动到eax寄存器中(栈传值)
	movl	%eax, -8(%ebp)      # 把eax寄存器的值放入到ebp-8的位置。
	movl	-8(%ebp), %eax      # 把ebp-8的位置的值移动到eax寄存器中
	movl	-4(%ebp), %edx      # 把ebp-4的位置的值移动到edx寄存器中
    addl	%edx, %eax          # 把edx寄存器的值和eax寄存器的值相加。并且放到eax寄存器中。
	popl	%ebp                # 将ebp的值弹出,这样就知道上一个栈帧的位置
	ret                         # 将eip的值弹出,这样就知道下一行执行的代码位置

注释特别特别的详细了。但是用注释+图片来理解更佳,所以看到下图。

 在32位机中,方法的传参是通过栈的方法来传递。在32位机中,地址和int都是占用4个字节,所以在add_value开辟的栈帧中,直接用ebp寄存器+8和+12就可以获取到main方法中准备好的方法参数。而通过汇编可以得知,这里是mov的真实值,并没有通过lea指令取到地址。所以在add_value栈帧中,任意的操作传入的参数不会影响到main方法中的变量。

 

add_reference方法汇编:

add_reference:
	pushl	%ebp                # 将上个栈帧的ebp地址压入当前栈帧(为了返回)
	movl	%esp, %ebp          # 将esp和ebp保持在同一个位置
    subl	$16, %esp           # 开辟16个字节大小的栈空间。
	movl	8(%ebp), %eax       # 重点:将ebp+8位置的值移动到eax寄存器
    movl	%eax, -4(%ebp)      # 把eax寄存器存放的地址放入到ebp-4的位置。
	movl	12(%ebp), %eax      # 重点:将ebp+12位置的值移动到eax寄存器
	movl	%eax, -8(%ebp)      # 把eax寄存器存放的地址放入到ebp-8的位置。
    movl	-4(%ebp), %eax      # 把ebp-4位置的值移动到eax寄存器中
	movl	(%eax), %edx        # eax寄存器解引用(获取到地址对应的真实值),并且放入到edx寄存器中。
	movl	-8(%ebp), %eax      # 把ebp-8位置的值移动到eax寄存器中。
	movl	(%eax), %eax        # eax寄存器解引用(获取到地址对应的真实值),并且放入到eax寄存器中。
	addl	%edx, %eax          # 将edx和eax寄存器的值相加
	popl	%ebp                # 将ebp的值弹出,这样就知道上一个栈帧的位置
	ret                         # 将eip的值弹出,这样就知道下一行执行的代码位置

注释特别特别的详细了。但是用注释+图片来理解更佳,所以看到下图。 

  在32位机中,方法的传参是通过栈的方法来传递。在32位机中,地址和int都是占用4个字节,所以在add_value开辟的栈帧中,直接用ebp寄存器+8和+12就可以获取到main方法中准备好的方法参数。但是在main方法中的汇编,可以看到使用了lea指令取到了int a和int b的地址。并且通过栈传递方法参数到add_reference方法中,所以在add_reference方法中,只要操作了这两个传入的地址参数就等同于在操作int a和int b变量。

 

总结:

一言以蔽之:

值传递:mov移动的真实值,等同于复制了一份真实值。所以操作复制的那一份,原本的不受影响

引用传递:通过lea指令获取到地址,再通过mov移动地址,也等同于复制了一份地址。但是地址最终是指向了真实值,所以后续操作复制的那一份地址也会影响到地址对应的真实值。

 最后,如果本帖对您有一定的帮助,希望能点赞+关注+收藏!您的支持是给我最大的动力,后续会一直更新各种框架的使用和框架的源码解读~!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程序员李哈

创作不易,希望能给与支持

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

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

打赏作者

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

抵扣说明:

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

余额充值