龙空技术网

c++回头看-从汇编角度理解函数参数值传递和引用传递

明说网络 241

前言:

此刻同学们对“函数传值和传引用的区别”大约比较关注,大家都需要了解一些“函数传值和传引用的区别”的相关内容。那么小编同时在网摘上汇集了一些对于“函数传值和传引用的区别””的相关文章,希望各位老铁们能喜欢,各位老铁们快快来学习一下吧!

示例程序

main.c

主要分为两个部分,每个部分使用一个display函数,函数内使得传入的参数自加1,然后打印到标准输出上。不同的地方在于,display1使用了值传递,display2使用了引用传递

#include <stdio.h>void display1(int num){ //int num,属于值传递num++;printf("display1: %d\n", num);}void display2(int & num){ //int & num ,属于引用传递num++;printf("display2: %d\n", num);}int main(){int num1 = 0, num2 = 0;display1(num1);printf("num1:%d\n", num1);printf("--------------------\n");display2(num2);printf("num2:%d\n", num2);return 0;}

makefile

OBJ=reference$(OBJ):g++ main.c -o $@clean:-rm -rf $(OBJ)

在ubuntu中使用make命令进行编译并运行,结果如下图所示。

通过上述结果我们可以看出,虽然仅仅一个&符号的差异,但通过参数传递和通过值传递获得的结果不一样。

值传递中的num虽然进行了自加操作(输出display:1可以看出),但是并没有影响到main函数中的num1(num1:0可以看出)但是引用传递中的num进行了自加1(输出display:1可以看出),并且影响到了main函数中的num2(num2:1可以看出).提出问题

是什么原因造成了仅仅一个&符号的差异,导致函数内值传递和引用传递的差别呢?

实验

objdump -d ./reference > objdump.txt./reference: file format elf64-x86-6400000000004005d6 <_Z8display1i>:4005d6: 55 push %rbp4005d7: 48 89 e5 mov %rsp,%rbp4005da: 48 83 ec 10 sub $0x10,%rsp4005de: 89 7d fc mov %edi,-0x4(%rbp) #将值取出到%rbp-0x44005e1: 83 45 fc 01 addl $0x1,-0x4(%rbp) # +1运算4005e5: 8b 45 fc mov -0x4(%rbp),%eax #写回%rbp-0x4, 仍然是局部变量,生命周期在函数内4005e8: 89 c6 mov %eax,%esi4005ea: bf 44 07 40 00 mov $0x400744,%edi4005ef: b8 00 00 00 00 mov $0x0,%eax4005f4: e8 b7 fe ff ff callq 4004b0 <printf@plt>4005f9: 90 nop4005fa: c9 leaveq 4005fb: c3 retq 00000000004005fc <_Z8display2Ri>:4005fc: 55 push %rbp4005fd: 48 89 e5 mov %rsp,%rbp400600: 48 83 ec 10 sub $0x10,%rsp400604: 48 89 7d f8 mov %rdi,-0x8(%rbp)#将值取出到%rbp-0x8, 注意此时%rdi为地址400608: 48 8b 45 f8 mov -0x8(%rbp),%rax40060c: 8b 00 mov (%rax),%eax40060e: 8d 50 01 lea 0x1(%rax),%edx #加一400611: 48 8b 45 f8 mov -0x8(%rbp),%rax400615: 89 10 mov %edx,(%rax) # 将结果放入原地址所指的内存当中,400617: 48 8b 45 f8 mov -0x8(%rbp),%rax40061b: 8b 00 mov (%rax),%eax40061d: 89 c6 mov %eax,%esi40061f: bf 52 07 40 00 mov $0x400752,%edi400624: b8 00 00 00 00 mov $0x0,%eax400629: e8 82 fe ff ff callq 4004b0 <printf@plt>40062e: 90 nop40062f: c9 leaveq 400630: c3 retq 0000000000400631 <main>:400631: 55 push %rbp400632: 48 89 e5 mov %rsp,%rbp400635: 48 83 ec 10 sub $0x10,%rsp400639: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax400640: 00 00 400642: 48 89 45 f8 mov %rax,-0x8(%rbp)400646: 31 c0 xor %eax,%eax400648: c7 45 f4 00 00 00 00 movl $0x0,-0xc(%rbp)40064f: c7 45 f0 00 00 00 00 movl $0x0,-0x10(%rbp)400656: 8b 45 f4 mov -0xc(%rbp),%eax # 将%rbp-0xc的值放入%eax,相当于复制了一份400659: 89 c7 mov %eax,%edi40065b: e8 76 ff ff ff callq 4005d6 <_Z8display1i>400660: 8b 45 f4 mov -0xc(%rbp),%eax400663: 89 c6 mov %eax,%esi400665: bf 60 07 40 00 mov $0x400760,%edi40066a: b8 00 00 00 00 mov $0x0,%eax40066f: e8 3c fe ff ff callq 4004b0 <printf@plt>400674: bf 69 07 40 00 mov $0x400769,%edi400679: e8 12 fe ff ff callq 400490 <puts@plt>40067e: 48 8d 45 f0 lea -0x10(%rbp),%rax # 将%rbp-0xc的地址放入%eax,想到与对原地址进行操作400682: 48 89 c7 mov %rax,%rdi400685: e8 72 ff ff ff callq 4005fc <_Z8display2Ri>40068a: 8b 45 f0 mov -0x10(%rbp),%eax40068d: 89 c6 mov %eax,%esi40068f: bf 7e 07 40 00 mov $0x40077e,%edi400694: b8 00 00 00 00 mov $0x0,%eax400699: e8 12 fe ff ff callq 4004b0 <printf@plt>40069e: b8 00 00 00 00 mov $0x0,%eax4006a3: 48 8b 55 f8 mov -0x8(%rbp),%rdx4006a7: 64 48 33 14 25 28 00 xor %fs:0x28,%rdx4006ae: 00 00 4006b0: 74 05 je 4006b7 <main+0x86>4006b2: e8 e9 fd ff ff callq 4004a0 <__stack_chk_fail@plt>4006b7: c9 leaveq 4006b8: c3 retq 4006b9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)

通过上述汇编代码(相关关键步骤已经使用注释进行了说明)。

display1(num1);display2(num2);
400656: 8b 45 f4 mov -0xc(%rbp),%eax # 注意mov操作!!将%rbp-0xc的值(也就是局部变量num1)放入%eax,相当于复制了一份400659: 89 c7 mov %eax,%edi40065b: e8 76 ff ff ff callq 4005d6 <_Z8display1i> 40067e: 48 8d 45 f0 lea -0x10(%rbp),%rax # 注意lea 操作!!将%rbp-0xc(也就是局部变量num2)的地址放入%eax,想当于对原地址进行操作400682: 48 89 c7 mov %rax,%rdi400685: e8 72 ff ff ff callq 4005fc <_Z8display2Ri>

可以看出:

对于值传递,使用mov指令,相当于复制了一份;对于引用,使用lea指令,得到了地址,随后的操作都在地址上进行,相当于直接对该地址的数进行操作。

因此,我们知道,虽然传递的都是传递的一个变量名,但display1使用的值传递,display2使用的是引用传递:

display1(num1);//虽然进行了自加1,但是是对num1的副本进行的操作,作用范围在display函数内display2(num2);//使用引用传递,相当于指针操作,作用范围在main函数当中。
当使用值传递时,在函数内对参数的操作,参数作用范围只在函数内,跳出函数后该是啥还是啥,在原函数(这里是main)里就是进入函数前的状态。因为值传递方式,在函数中只改变的是值的副本。在使用引用传递时,引用的本质使用的是指针。因此在函数中的操作,都会直接作用于该地址的值。总结

通过对值传递和引用传递的汇编代码的分析,我们清晰的看出值传递本是上是传递了一个原值的副本,其变化并不影响调用函数的值;引用传递的本质是指针,其变化,直接作用于调用函数的值。

标签: #函数传值和传引用的区别