zoukankan      html  css  js  c++  java
  • 程序设计基石与实践系列之按值传递还是按引用

    从简单的样例開始.如果我们要交换两个整形变量的值,在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;
    }
    由于样例非常easy。看代码就可以知道仅仅有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)
    }
    上面这段汇编的逻辑非常easy,我们看到里面的关键点都在强调:将值存放在实际地址中.上面这句话尽管简单,但非常重要。能够拆为两点:

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

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

    // c代码
    call_by_val_ptr(&a,&b);
     
    // 相应的汇编代码
     
    lea    -0x18(%rbp),%rdx
    lea    -0x14(%rbp),%rax
    mov    %rdx,%rsi
    mov    %rax,%rdi
    callq  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),%edx
    mov    -0x14(%rbp),%eax
    mov    %edx,%esi
    mov    %eax,%edi
    callq  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),%rdx
    lea    -0x14(%rbp),%rax
    mov    %rdx,%rsi
    mov    %rax,%rdi
    callq  4008ec <_Z15call_by_val_ptrPiS_>

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

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

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








  • 相关阅读:
    delphi Form属性设置 设置可实现窗体无最大化,并且不能拖大拖小
    学习 TTreeView [1]
    学习 TTreeView [3]
    学习官方示例
    踩方格(找规律 递推)
    踩方格(找规律 递推)
    普及组2008NOIP 排座椅(贪心+排序)
    普及组2008NOIP 排座椅(贪心+排序)
    POJ_3740 Easy Finding ——精确覆盖问题,DLX模版
    POJ_3740 Easy Finding ——精确覆盖问题,DLX模版
  • 原文地址:https://www.cnblogs.com/lxjshuju/p/7066369.html
Copyright © 2011-2022 走看看