笔记 | 计算机系统基础:05-按值传递和按地址传递为什么不同?

2020-01-10  本文已影响0人  KPlayer

上一篇《笔记 | 袁春风《计算机系统基础》:04-函数调用时发生了什么?》详细介绍了函数调用过程对应的机器级表示。理解了函数调用的具体过程,我们就能从底层的角度来理解函数调用时参数按值传递按址传递之间有什么区别。因此如果各位同学还不太了解函数调用的机器级过程,建议先认真阅读上一篇文章,方便更好的理解。

我们先来看下面两个程序代码:

#include <stdio.h>
main ( )
{
  int a=15, b=22;
  printf (“a=%d\tb=%d\n”, a, b);
  swap (a, b);
  printf (“a=%d\tb=%d\n”, a, b);
}

swap (int x, int y )
{
  int t=x;
  x=y;
  y=t;
}
#include <stdio.h>
main ( )
{
  int a=15, b=22;
  printf (“a=%d\tb=%d\n”, a, b);
  swap (&a, &b);
  printf (“a=%d\tb=%d\n”, a, b);
}

swap (int *x, int *y )
{
  int t=*x;
  *x=*y;
  *y=t;
}

我相信大部分人都知道两个程序的输出结果。
第一个运行结果为:

a=15 b=22
a=15 b=22

第二个运行结果为:

a=15 b=22
a=22 b=15

并且大部分人都能给出解释:“因为第一个是按值传递,所以没有改变原变量的值,第二个是按址传递,所以改变了原变量的值,完成了交换”。

那么如果再问得更深入一点呢?在函数调用过程中,按址传递是怎么改变原变量的值呢?

回忆函数调用的过程(见上节),一般需要先形成栈帧,分配局部变量空间,然后将实参送栈帧入口参数处,保存返回地址并转被调用函数。

我们来看一下按值传递的程序的栈帧情况:

按值传递

首先main函数分配自己的局部变量,把变量15放在了ebp-4的地方,把变量22放在ebp-8的地方。然后准备入口参数,把ebp-8(b=22)放在esp+4的地方,把ebp-4(a=15)放在esp的地方(这里用了movl指令,把值放在了入口参数处),然后调用call指令,先保存返回地址,再执行swap函数。(这时从高地址到低地址应该是:22,15,返回地址)

swap函数先执行pushl %ebpmovl %esp, %ebp形成自己的帧底,此时新的ebp+8就对应着入口参数15,ebp+12就对应着入口参数22。swap取入口参数15(ebp+8)赋给了edx,22(ebp+12)赋给了eax,然后开始做交换,把eax赋给ebp+8的地方,把edx赋给ebp+12的地方,最终形成了上图所示的栈帧情况。

可以看出,这里的swap只是改变了入口参数所在的地址空间内容,对原有main中分配的局部变量没有任何影响,所以当swap返回后,main中的变量并没有改变。

那么按址传递有什么不同呢?

按址传递

同样的,首先main函数分配自己的局部变量,把变量15放在了ebp-4的地方,把变量22放在ebp-8的地方。然后准备入口参数,这里有所不同了,用了leal地址传送指令,而不再是之前的movl传值指令!!!也就是把ebp-8(b=22)的地址放在esp+4的地方,把ebp-4(a=15)的地址放在esp的地方,然后调用call指令,先保存返回地址,再执行swap函数。(这时从高地址到低地址应该是:&b,&a,返回地址

类似的,swap函数先执行pushl %ebpmovl %esp, %ebp形成自己的帧底,此时新的ebp+8就对应着入口参数&a,ebp+12就对应着入口参数&b。接着swap保存了ebx寄存器内容(因为后面要借用这个寄存器,所以先保存原值,待借用完后恢复给main)。

然后我们注意到,在执行完movl 8(%ebp), %edx之后,相比按值传送,这里多做了一个movl (%edx), %ecx,因为此时edx里存的是地址&a,必须通过寻址后才能把对应的值15赋给ecx寄存器;同样的通过寻址把22赋给了ebx寄存器。

然后开始交换,把ebx里存的22赋给了edx(存的是地址,&a)所指向的地方,也就是main中局部变量a存储的地方,把ecx里存的15赋给了eax(存的是地址,&b)所指向的地方,也就是main中局部变量b存储的地方。这样原来main中存放局部变量a的地方值变成了22,存放局部变量b的地方值变成了15,如上图所示。

可以看出,由于入口参数处存放的是局部变量的地址,swap通过寻址改变了原存放局部变量内存空间的值,进行了交换。

总结以上,如果从指令层面分析,按值传递和按址传递不同之处在于:

  1. 准备入口参数时,用的是movl还是leal指令,也就是传值还是传址。
  2. 对参数变量进行修改时,是否通过寻址方式改变了原调用函数局部变量的值。
上一篇下一篇

猜你喜欢

热点阅读