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

从简单的例子开始.假设我们要交换两个整形变量的值,在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),%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中的交换,从汇编代码就能看出,只是交换了指针指向的地址,而没有通过将值存入这个地址而改变地址中的值

时间: 2024-10-11 13:03:57

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

程序设计基石与实践系列之C语言程序员必读的5本书

英文出处:fromdev:best-c-programming-books 你正计划着通过看书来学习C语言吗?"书籍是人类最忠诚的朋友".海明威一定知道书籍对一个人一生的重要性.书籍是知识的丰富来源.你可以从书中学到各种知识.书籍可以毫无歧视地向读者传达作者的本意.C语言是由 Dennis Ritchie在1969年到1973年在贝尔实验室研发的.C语言可以把程序简单地编译为机器指令,使得它成为了最高效的语言. 为什么在程序员中,C语言如此流行呢?这背后有很多原因.首先,它独立于平台,

程序设计基石与实践系列之成为一名Top的C语言程序员

英文出处:Fabien Sanglard -To become a good C programmer 问题的提出 每过一段时间我总会收到一些程序员发来的电子邮件,他们会问我是用什么编程语言来编写自己的游戏的,以及我是如何学习这种编程语言的.因此,我认为在这篇博文里列出一些有关C语言的最佳读物应该能帮到不少人.如果你知道其它的优秀读物,请给我发邮件或者直接在评论栏中告诉我吧. 问题的解答 我在之前的一篇博文中已经提到过了,目前为止,所有我所编写的商业3D引擎95%都是C89(也称作标准C,或AN

程序设计基石与实践系列之能让你成为Top程序员的十个C语言资源

英文出处:mycplus ---top-ten-c-language-resources 一些人觉得编程无聊,一些人觉得它很好玩.但每个程序员都必须紧跟编程语言的潮流.大多数程序员都是从C开始学习编程的,因为C是用来写操作系统.应用程序最常用的语言. C编程笔记 : 这些是华盛顿实验学院C编程入门课的部分笔记.它们是以<c程序设计语言>的补充笔记(从1995年春开始)为基础修改的,而这本书的作者就是大名鼎鼎的Brian Kernighan和Dennis Ritchie了,人们亲昵地称呼他们为K

程序设计基石与实践系列之编写高效的C程序与C代码优化

原文出处: codeproject:Writing Efficient C and C Code Optimization 虽然对于优化C代码有很多有效的指导方针,但是对于彻底地了解编译器和你工作的机器依然无法取代,通常,加快程序的速度也会加大代码量.这些增加的代码也会影响一个程序的复杂度和可读性,这是不可接受的,比如你在一些小型的设备上编程,例如:移动设备.PDA--,这些有着严格的内存限制,于是,在优化的座右铭是:写代码在内存和速度都应该优化. 整型数 / Integers 在我们知道使用的

程序设计基石与实践系列之类型提升、内存分配,数组转指针、打桩和矢量变换

英文出处:Peter Fa?ka: Guide to Advanced Programming in C C语言可用于系统编程.嵌入式系统中,同时也是其他应用程序可能的实现工具之一. 当你对计算机编程怀有强烈兴趣的时候,却对C语言不感冒,这种可能性不大.想全方位地理解C语言是一件极具挑战性的事. Peter Fa?ka 在2014年1月份写下了这篇长文,内容包括:类型提升.内存分配,数组转指针.显式内联.打桩(interpositioning)和矢量变换. 整型溢出和类型提升 多数C程序员以为,

程序设计基石与实践系列之失落的C语言结构体封装艺术

英文来源于 Eric S. Raymond-- The Lost Art of C Structure Packing 谁该阅读这篇文章 本文是关于削减C语言程序内存占用空间的一项技术--为了减小内存大小而手工重新封装C结构体声明.你需要C语言的基本知识来读懂本文. 如果你要为内存有限制的嵌入式系统.或者操作系统内核写代码,那么你需要懂这项技术.如果你在处理极大的应用程序数据集,以至于你的程序常常达到内存的界限时,这项技术是有帮助的.在任何你真的真的需要关注将高速缓存行未命中降到最低的应用程序里

程序设计基石与实践系列之C语言未定义行为一览

英文出处:Christopher Cole: a glimpse of undefined behavior in c 几周前,我的一位同事带着一个编程问题来到我桌前.最近我们一直在互相考问C语言的知识,所以我微笑着鼓起勇气面对无疑即将到来的地狱. 他在白板上写了几行代码,并问这个程序会输出什么? #include <stdio.h> int main(){ int i = 0; int a[] = {10,20,30}; int r = 1 * a[i++] + 2 * a[i++] + 3

程序设计基石与实践之C语言指针和数组基础

英文出处:Dennis Kubes:  <Basics of Pointers and Arrays in C>. 关于C语言中指针和数组的争论就像是一场恶战.一方面,有些人觉得,所有人都必须承认指针与数组是不同的.而另一些人则认为数组被当成指针来处理,因此它们不应该有什么区别.这种现象让人迷惑.然而,这两种说法其实都是正确的. 数组不是指针,指针也不能说是数组.在C语言中,指针仅在内存中代表一个地址,而数组是许多连续的内存块,多个类型相似的元素存储在其中.更深入的解释,请参考我之前的博文&l

Azure实践系列 4:启用和配置免费的MFA

在上一篇的实践系列中我们已经配置好了我们云端的Azure AD,我们可以将云服务器加入到域,也可以添加用户到云端的Azure AD中,那相对于现在这有一个移动为先云为先的时代,云端的Azure AD和传统的AD相比还有什么优势呢?第一,云端的Azure AD使用方便,其次不需要我们维护域控制器,也不需要我们投入相应的硬件,所有的管理只需要一个浏览器,配合鼠标与键盘.那就只有这点优势了吗,当然不是,云端Azure AD还提供免费的不限次数没有任何费用收取的多重身份验证服务MFA. 在开始本篇的实践