程序设计基石与实践系列之按值传递还是按引用

来源:互联网 发布:c语言读文本文件 编辑:程序博客网 时间:2024/05/02 02:47

从简单的例子开始.假设我们要交换两个整形变量的值,在C/C++中怎么做呢?我们来看多种方式,哪种能够做到.

void call_by_ref(int &p,int &q) { // 可以交换的例子    int t = p;    p = q;    q = t;} void call_by_val_ptr(int * p,int * q) { // 不能交换的例子    int * t = p;    p = q;    q = t;} void call_by_val(int p,int q){ // 不能交换的例子    int t = p ;    p = q;    q = t;}
因为例子非常简单,看代码即可知道只有call_by_ref这个方法可以成功交换。这里,你一定还知道一种可以交换的方式,别着急,慢慢来,我们先看看为什么只有call_by_ref可以交换。

call_by_ref

void call_by_ref(int &p,int &q) {    push   %rbp    mov    %rsp,%rbp    mov    %rdi,-0x18(%rbp)    mov    %rsi,-0x20(%rbp)     //int t = p;    mov    -0x18(%rbp),%rax    //关键点:rax中存放的是变量的实际地址,将地址处存放的值取出放到eax中    mov    (%rax),%eax    mov    %eax,-0x4(%rbp)     //p = q;    mov    -0x20(%rbp),%rax    //关键点:rax中存放的是变量的实际地址,将地址处存放的值取出放到edx    mov    (%rax),%edx    mov    -0x18(%rbp),%rax    mov    %edx,(%rax)     //q = t;    mov    -0x20(%rbp),%rax    mov    -0x4(%rbp),%edx    //关键点:rax存放的也是实际地址,同上.    mov    %edx,(%rax)}
上面这段汇编的逻辑非常简单,我们看到里面的关键点都在强调:将值存放在实际地址中.上面这句话虽然简单,但很重要,可以拆为两点:

1、要有实际地址.
2、要有将值存入实际地址的动作.

从上面的代码中,我们看到已经有“存值”这个动作,那么传入的是否实际地址呢?

// c代码call_by_val_ptr(&a,&b); // 对应的汇编代码 lea    -0x18(%rbp),%rdxlea    -0x14(%rbp),%raxmov    %rdx,%rsimov    %rax,%rdicallq  4008c0 <_Z11call_by_refRiS_>

注意到,lea操作是取地址,那么就能确定这种“按引用传递“的方式,实际是传入了实参的实际地址。

那么,满足了上文的两个条件,就能交换成功。

call_by_val


call_by_val的反汇编代码如下:

void call_by_val(int p,int q){    push   %rbp    mov    %rsp,%rbp    mov    %edi,-0x14(%rbp)    mov    %esi,-0x18(%rbp)     //int t = p ;    mov    -0x14(%rbp),%eax    mov    %eax,-0x4(%rbp)     //p = q;    mov    -0x18(%rbp),%eax    mov    %eax,-0x14(%rbp)     //q = t;    mov    -0x4(%rbp),%eax    mov    %eax,-0x18(%rbp)}
可以看到,上面的代码中在赋值时,仅仅是将某种”值“放入了寄存器,再观察下传参的代码:

call_by_val(a,b); // 对应的汇编代码mov    -0x18(%rbp),%edxmov    -0x14(%rbp),%eaxmov    %edx,%esimov    %eax,%edicallq  400912 <_Z11call_by_valii>

可以看出,仅仅是将变量a、b的值存入了寄存器,而非”地址“或者能找到其”地址“的东西。那么,因为不满足上文的两个条件,所以不能交换。

这里还有一点有趣的东西,也就是我们常听说的拷贝(Copy):当一个值,被放入寄存器或者堆栈中,其拥有了新的地址,那么这个值就和其原来的实际地址没有关系了,这种行为,是不是很像一种拷贝?

但实际上,在我看来,这是一个很误导的术语,因为上面的按引用传递的call_by_ref实际上也是拷贝一种值,它是个地址,而且是实际地址。

所以,应该记住的是那两个条件,在你还不能真正理解拷贝的意义之前最好不要用这个术语。

call_by_val_ptr


这种方式,本来是可以完成交换的,因为我们可以用指针来指向实际地址,这样我们就满足了条件1:

要有实际地址。

别着急,我们先看下上文的实现中,为什么没有完成交换:

void call_by_val_ptr(int * p,int * q) {    push   %rbp    mov    %rsp,%rbp    mov    %rdi,-0x18(%rbp)    mov    %rsi,-0x20(%rbp)    //int * t = p;    mov    -0x18(%rbp),%rax    mov    %rax,-0x8(%rbp)    //p = q;    mov    -0x20(%rbp),%rax    mov    %rax,-0x18(%rbp)    //q = t;    mov    -0x8(%rbp),%rax    mov    %rax,-0x20(%rbp)}
可以看到,上面的逻辑和call_by_val非常类似,也只是做了将值放到寄存器这件事,那么再看下传给它的参数:

call_by_val_ptr(&a,&b); // 对应的汇编代码lea    -0x18(%rbp),%rdxlea    -0x14(%rbp),%raxmov    %rdx,%rsimov    %rax,%rdicallq  4008ec <_Z15call_by_val_ptrPiS_>

注意到,lea是取地址,所以这里实际也是将地址传进去了,但为什么没有完成交换?

因为不满足条件2:将值存入实际地址。

call_by_val_ptr中的交换,从汇编代码就能看出,只是交换了指针指向的地址,而没有通过将值存入这个地址而改变地址中的值








0 0
原创粉丝点击