程序設計基石與實踐系列之按值傳遞還是按引用
阿新 • • 發佈:2017-06-22
有趣 name align pos str 堆棧 技術分享 easy pan
由於樣例非常easy。看代碼就可以知道僅僅有call_by_ref這種方法能夠成功交換。這裏,你一定還知道一種能夠交換的方式,別著急。慢慢來,我們先看看為什麽僅僅有call_by_ref能夠交換。 上面這段匯編的邏輯非常easy,我們看到裏面的關鍵點都在強調:將值存放在實際地址中.上面這句話盡管簡單,但非常重要。能夠拆為兩點:
從簡單的樣例開始.如果我們要交換兩個整形變量的值,在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
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),%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中的交換。從匯編代碼就能看出,僅僅是交換了指針指向的地址,而沒有通過將值存入這個地址而改變地址中的值。
程序設計基石與實踐系列之按值傳遞還是按引用