免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 2202 | 回复: 10
打印 上一主题 下一主题

[C++] [原]"引用"是怎么工作的 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-02-27 12:46 |只看该作者 |倒序浏览
不纠缠于语法的解释,看代码和汇编结果最直接。举下面这个小例子程序: (gcc -masm=hello -S main.cpp可以得到汇编代码)
#include<stdio.h>
int x=3;
int f1(){return x;}
int& f2(){return x;}
int main(){
int a=f1();
int y=f2();
y=4;//仍然有x=3
int&z=f2();
z=5;
printf("x=%d,y=%d",x,y);//z改变了x
return 0;
}
输出是什么呢? x=5,y=4

分析:
f2是个返回引用的函数,当且仅当int&z =f2()的时候才是真的返回引用,int y=f2()返回的仍然是一个值的拷贝
--------------------------------------------------------------------------------------------------------------------
f1和f2的定义:
.globl __Z2f1v
        .def        __Z2f1v;        .scl        2;        .type        32;        .endef
__Z2f1v:
        push        ebp
        mov        ebp, esp
        mov        eax, DWORD PTR _x              f1()返回一个值的拷贝
        pop        ebp
        ret
        .align 2
.globl __Z2f2v
        .def        __Z2f2v;        .scl        2;        .type        32;        .endef
__Z2f2v:
        push        ebp
        mov        ebp, esp
        mov        eax, OFFSET FLAT:_x             f2()返回的就是一个地址,不是值
        pop        ebp
        ret
        .def        ___main;        .scl        2;        .type        32;        .endef
        .section .rdata,"dr"
我们看一下main函数
_main:
        push        ebp
        mov        ebp, esp
        sub        esp, 40
        and        esp, -16
        mov        eax, 0
        add        eax, 15
        add        eax, 15
        shr        eax, 4
        sal        eax, 4
        mov        DWORD PTR [ebp-16], eax
        mov        eax, DWORD PTR [ebp-16]
        call        __alloca
        call        ___main
        call        __Z2f1v                                 -> 调用f1(), 返回值放在eax
        mov        DWORD PTR [ebp-4], eax      -> eax赋值给a
        call        __Z2f2v
        mov        eax, DWORD PTR [eax]         -> 调用f2(), 返回x的值拷贝放在eax
        mov        DWORD PTR [ebp-8], eax     ->  eax赋值给y
        mov        DWORD PTR [ebp-8], 4         ->  立即数"4"赋值给y. y的改变不会改变x!!!!!!
        call        __Z2f2v
        mov        DWORD PTR [ebp-12], eax   -> 调用f2(), 返回x的地址给z
        mov        eax, DWORD PTR [ebp-12]   -> x的地址放入eax
        mov        DWORD PTR [eax], 5            -> 赋值5给eax指向的地址x
        mov        eax, DWORD PTR [ebp-8]    //以下是printf的调用
        mov        DWORD PTR [esp+8], eax
        mov        eax, DWORD PTR _x
        mov        DWORD PTR [esp+4], eax
        mov        DWORD PTR [esp], OFFSET FLAT:LC0
        call        _printf
        mov        eax, 0
        leave
        ret
        .def        _printf;        .scl        2;        .type        32;        .endef

结论:
     我们生命了一个int& f()这样的函数,但是在使用的时候并没有int& z=f(),那么仍然是去返回了一个值的拷贝,如果是对象的话有调用了一个operator=拷贝构造函数。因此必须在使用的时候记住T& t=f()去得到函数返回的引用。
   当然,如果f()返回的不是引用的话,T&t 得到当前调用堆栈上面的临时对象,不能在当前上下文之外引用,否则可能引起"未定义"的行为。

[ 本帖最后由 jeanlove 于 2009-2-27 13:12 编辑 ]

论坛徽章:
0
2 [报告]
发表于 2009-02-27 12:52 |只看该作者
结论,结论,结论,我们要看结论

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
3 [报告]
发表于 2009-02-27 13:23 |只看该作者
首先第一句话我就不同意。
引用纯粹就是语法层次的概念,怎么能抛弃语法不讲呢。
语法认识到位,语义理解清晰,就不会用错了,至于如何实现,倒在其次。

论坛徽章:
0
4 [报告]
发表于 2009-02-27 16:08 |只看该作者
原帖由 flw 于 2009-2-27 13:23 发表
首先第一句话我就不同意。
引用纯粹就是语法层次的概念,怎么能抛弃语法不讲呢。
语法认识到位,语义理解清晰,就不会用错了,至于如何实现,倒在其次。


我根本就看不懂这个实现

[ 本帖最后由 雨过白鹭洲 于 2009-2-27 16:34 编辑 ]

论坛徽章:
0
5 [报告]
发表于 2009-02-27 16:18 |只看该作者
mov        eax, DWORD PTR [eax]         -> 调用f2(), 返回x的值拷贝放在eax

啊啊

论坛徽章:
0
6 [报告]
发表于 2009-02-27 17:21 |只看该作者
原帖由 flw 于 2009-2-27 13:23 发表
首先第一句话我就不同意。
引用纯粹就是语法层次的概念,怎么能抛弃语法不讲呢。
语法认识到位,语义理解清晰,就不会用错了,至于如何实现,倒在其次。


理解不一样吧,单纯从语法层次能否区分int& z=f2()和int z=f2()是否都真的操作了f2返回的引用呢? 我感觉是比较困难的,但是看一下实现,就很明白了,这个地方编译器做了什么事情,纯从语法很难区分出来。

论坛徽章:
0
7 [报告]
发表于 2009-02-27 17:27 |只看该作者
int& z=f2()

这样写貌似不很妥当呀

论坛徽章:
8
CU大牛徽章
日期:2013-04-17 10:59:39CU大牛徽章
日期:2013-04-17 11:01:45CU大牛徽章
日期:2013-04-17 11:02:15CU大牛徽章
日期:2013-04-17 11:02:36CU大牛徽章
日期:2013-04-17 11:02:58技术图书徽章
日期:2013-12-04 10:48:50酉鸡
日期:2014-01-03 10:32:30辰龙
日期:2014-03-06 15:04:07
8 [报告]
发表于 2009-02-27 17:33 |只看该作者
"理论"是完美的,“实现”却往往是有缺憾甚至可以取巧的。

比如引用,从“理论”上说,它就是对同一个内存单元的多个命名,或者是一个会被编译器自动补全为(*p)的指针——到这里就足够了,别再钻了。否则定然走火入魔。


如果再钻,去看编译器实际生成的代码,比如,inline int get_value(int & a) {return a;}

反汇编之,你可能会“震惊”的发现它似乎还是被还原为传值了,甚至根本就没生成代码(给优化没了)。

其实说穿了很容易理解:
  编译器发现你并没有修改a的值,返回的东西也不会导致a被修改;那么,如果直接把a的值放ax内传进去,岂不可以节约一次内存访问?
  于是,get_value就被优化成传值了。

  进一步分析还可以发现,调用者仅仅是调用了一下get_value,并没有用变量接收它的返回值;那么这个调用就完全可以“短路”掉。
  于是,get_value这个调用就被优化没了。

当然,不同的编译器,所能做到的优化程度是不一样的。


c++遵循“鸭子哲学”,意思就是只要最终结果符合标准规定,随你背后怎么折腾。
所以学c++,搞明白标准规定就足够了;除非研究编译器,否则别管人家背后折腾什么。

不然的话,你学到的已经不是“引用”这个概念本身,而是“xx编译器yy版zz编译参数下被折腾的不成人形”的引用。

论坛徽章:
8
CU大牛徽章
日期:2013-04-17 10:59:39CU大牛徽章
日期:2013-04-17 11:01:45CU大牛徽章
日期:2013-04-17 11:02:15CU大牛徽章
日期:2013-04-17 11:02:36CU大牛徽章
日期:2013-04-17 11:02:58技术图书徽章
日期:2013-12-04 10:48:50酉鸡
日期:2014-01-03 10:32:30辰龙
日期:2014-03-06 15:04:07
9 [报告]
发表于 2009-02-27 17:39 |只看该作者
“概念”是静态的,“实物”是动态的。

想知道什么是汽车,不必把邻居家那辆破车缺了的那个大灯记在心上。

——————————————————————————
学术点说,概念有内涵和外延之分;不同概念作用在一起,还可能会衍生出更多的概念。



想把东西学到手,隔离、提取概念的能力至关重要。

如果怕人学懂一件事,那么从破损的大灯开始说汽车是一个好办法——这是没灯的汽车,那是轮胎爆了的汽车……这是黑色轿车,那是绿色卡车……白车非车,轿车也非车……

如此乱七八糟瞎扯下去,学生必然会对这位教授的渊博佩服到五体投地;但他们永远也甭想搞懂什么是汽车。。


一个一个概念准确区分并给出严格无二义的定义——别怕这些定义看上去有多“白”多“简单”——但只要知道了何谓“破”,何谓“车”,那么“破车”是什么自然就可以揣摩出来。

等到知道了白车、黑车、好车、坏车等等都是车这个概念的具体实例,那么自然也就知道“白车”这个具体概念不等同于“车”这个泛化概念。
然后才自然知道,所谓“白车非车”者,无非是高级装X犯而已。

学阴阳八卦,学不会装X就等于没学会;学现代科学技术,学不到简单处,也永不能算学会。

[ 本帖最后由 shan_ghost 于 2009-2-27 17:59 编辑 ]

论坛徽章:
0
10 [报告]
发表于 2009-02-27 22:42 |只看该作者
#include<stdio.h>
int x=3;
int f1(){return x;} //返回x拷贝值
int& f2(){return x;} //返回x地址
int main(){
int a=f1();
int y=f2();
y=4;//很明显你只是把y原来保存的x地址改成了4
int&z=f2();//z可以引用x了
z=5;//没错,你把x的值改掉了
printf("x=%d,y=%d",x,y);
return 0;
}
输出是什么呢? x=5,y=4

[ 本帖最后由 alphayeah 于 2009-2-27 22:48 编辑 ]
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP