C++中返回引用和返回值的区别详解(1)

2023-05-16

一、主要讨论下面两个函数的区别:


int& at()
{
    return m_data_;
}  

int at()
{
    return m_data_;
}  

上面两个函数,第一个返回值是int的引用int&,第二个返回值是int,二者的区别是什么呢?

我们先用一个语句 const int& a = mymay.at(); 来分别调用一次上面两个函数,然后看汇编语言的结果。

反汇编结果:


 1 #int& at()
 2 #{
 3 #    return m_data_;
 4 #}
 5 
 6 00BB6830  push        ebp  
 7 00BB6831  mov         ebp,esp  
 8 00BB6833  sub         esp,0CCh  
 9 00BB6839  push        ebx  
10 00BB683A  push        esi  
11 00BB683B  push        edi  
12 00BB683C  push        ecx  
13 00BB683D  lea         edi,[ebp-0CCh]  
14 00BB6843  mov         ecx,33h  
15 00BB6848  mov         eax,0CCCCCCCCh  
16 00BB684D  rep stos    dword ptr es:[edi]  
17 00BB684F  pop         ecx  
18 00BB6850  mov         dword ptr [this],ecx  
19         m_data_++;
20 00BB6853  mov         eax,dword ptr [this]  
21 00BB6856  mov         ecx,dword ptr [eax]  
22 00BB6858  add         ecx,1  
23 00BB685B  mov         edx,dword ptr [this]  
24 00BB685E  mov         dword ptr [edx],ecx  
25         return m_data_;
26 #取地址this中的值5879712(m_data_的地址)到寄存器eax中,此时寄存器eax存的是m_data_的地址
27 00BB6860  mov         eax,dword ptr [this]  
28     }
29 00BB6863  pop         edi  
30 00BB6864  pop         esi  
31 00BB6865  pop         ebx  
32 00BB6866  mov         esp,ebp  
33 00BB6868  pop         ebp  
34 00BB6869  ret  
35 
36 
37 
38 
39  
40     const int& a = mymay.at();    
41 00176AA2  lea         ecx,[mymay]  
42 00176AA5  call        MyMat::at (0171546h)  
43 #此时寄存器eax中的值为m_data_的地址5879712,直接将地址5879712存入地址a中。
44 00176AAA  mov         dword ptr [a],eax  
45     cout << a << endl;  


 1 #int at()
 2 #{
 3 #    return m_data_;
 4 #}
 5 
 6 
 7 012B6830  push        ebp  
 8 012B6831  mov         ebp,esp  
 9 012B6833  sub         esp,0CCh  
10 012B6839  push        ebx  
11 012B683A  push        esi  
12 012B683B  push        edi  
13 012B683C  push        ecx  
14 012B683D  lea         edi,[ebp-0CCh]  
15 012B6843  mov         ecx,33h  
16 012B6848  mov         eax,0CCCCCCCCh  
17 012B684D  rep stos    dword ptr es:[edi]  
18 012B684F  pop         ecx  
19 012B6850  mov         dword ptr [this],ecx  
20         return m_data_;
21 #和上面一样,也是先取出m_data_的地址
22 012B6853  mov         eax,dword ptr [this]
23 #和上面不一样,不是直接将m_data_的地址放入寄存器eax中,而是取地址5879712中的值(m_data_=3)放入寄存器eax中,此时寄存器eax存的是m_data_的值(3)
24 012B6856  mov         eax,dword ptr [eax]  
25     }
26 012B6858  pop         edi  
27 012B6859  pop         esi  
28 012B685A  pop         ebx  
29 012B685B  mov         esp,ebp  
30 012B685D  pop         ebp  
31 012B685E  ret  
32 
33 
34 
35 
36   
37     const int& a = mymay.at();    
38 008E6AA2  lea         ecx,[mymay]  
39 008E6AA5  call        MyMat::at (08E154Bh) 
40 #此时eax的值为3,将3存入地址ebp-24h中,
41 008E6AAA  mov         dword ptr [ebp-24h],eax 
42 #将eax的值变成ebp-24h 
43 008E6AAD  lea         eax,[ebp-24h]  
44 #将地址ebp-24h写到地址为a中,此时a代表的地址是ebp-24h
45 008E6AB0  mov         dword ptr [a],eax  
46     cout << a << endl;  

所以结论就是:

1、返回值为引用型(int& )的时候,返回的是地址,因为这里用的是 int& a=mymay.at(); ,所以a和m_data_指的是同一块地址(由寄存器eax传回的5879712)。

2、返回值不是引用型(int)的时候,返回的是一个数值。这个时候就很有意思了,编译器是先将这个数值放入一个内存中(上面例子中,该内存地址为ebp-24h),再把这个地址付给a,此时的a代表的地址是ebp-24h,和m_data_代表的地址不一样(m_data_代表的地址是5879712)。

3、综上两点可以看出,当返回的值不是引用型时,编译器会专门给返回值分配出一块内存的(例子中为ebp-24h)。

二、说明一下函数返回时,如果不是返回一个变量的引用,则一定会生成一个临时变量。

看下面的函数,返回的是t而不是&t,所以一定会有临时变量产生。


1 T function1(){
2     T t(0);
3     return t;
4 }
5 T x=function1();  

这里的过程是:
1.创建命名对象t
2.拷贝构造一个无名的临时对象,并返回这个临时对象
3.由临时对象拷贝构造对象x
4.T x=function1();这句语句结束时,析构临时对象
这里一共生成了3个对象,一个命名对象t,一个临时对象作为返回值,一个命名对象x。

下面的函数稍微复杂一定,它没有先定义一个中间变量t,看起来似乎是直接返回了一个临时变量。但实际上,如果不经过c++的优化,那么它并没有提高效率,因为它还是创建了3个对象。


1 T function2(){
2      return T(0);
3 }
4 T x=function2();  

这里的过程是:
1.创建一个无名对象
2.由无名对象拷贝构造一个无名的临时对象
3.析构无名对象,返回临时对象
4.由临时对象拷贝构造对象x
5.T x=function2()语句结束时,析构临时对象。
这里一共生成了3个对象,其中有2个对象都是马上被析构掉的,不能被后面的代码使用。既然是这样,那么就会有优化的余地,可以尝试着不要前面的两个临时变量。c++确实会做这样的优化,优化后的c++会避免匿名对象和临时对象这两个对象的生成,而直接生成x,这样就减少了两次对象生成-回收的消耗,提高了程序性能。

其实function1()这段代码也是会经过优化的,但因为临时对象t是一个命名对象,所以一定会被创建。存储返回值的临时对象是多余的,会被优化掉而不生成。
但是,程序员不应该依赖这种优化,因为c++不保证这种优化一定会做。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++中返回引用和返回值的区别详解(1) 的相关文章

随机推荐

  • c#求STDEV标准偏差方法

    public static float StDev float arrData 计算标准偏差 float xSum 61 0F float xAvg 61 0F float sSum 61 0F float tmpStDev 61 0F i
  • QT中QMap使用实例详解

    QMap QMultiMap属于关联式容器 xff0c 其底层结构是通过二叉树实现 xff0c 故其查找value的效率很快 QMap中的数据都是成对出现的 xff0c 第一个称为key xff08 键 xff09 xff0c 第二个称va
  • C++中函数调用的整个过程内存堆栈分配详解

    一个函数执行过程中堆栈分配情况实例详解 下面的例子可以完全展示不同的变量所占的内存区域 xff1a main cpp int a 61 0 全局初始化区 char p1 全局未初始化区 main int b 栈中 char s 61 34
  • vncserver创建与客户端连接

    1 xff0e 确认服务器端是否安装了 vncserver Vnc 相关依赖包 xff1a gtk vnc python 0 3 2 3 el5 vnc server 4 1 2 14 el5 gtk vnc 0 3 2 3 el5 vnc
  • C++程序内存分配方式概念与区别(堆与栈)

    一 内存布局 1 栈区 xff08 stack xff09 xff1a 由编译器自动分配释放 xff0c 存放函数的参数值 xff0c 局部变量值等 xff0c 其操作方法类似数据结构中的栈 2 堆区 xff08 heap xff09 xf
  • QT中QString 转换为 char *的几种方法

    Qt下 QString转char 的问题 char MenuButton getTextStr QString string QString str 61 string char text 61 NULL QByteArray ba 61
  • C++中构造函数什么时候会被调用(从本质上理解)

    在 C 43 43 程序中 xff0c 变量在定义时可以初始化 如果不进行初始化 xff0c 变量的初始值会是什么呢 xff1f 对全局变量和局部变量来说 xff0c 这个答案是不一样的 未初始化的全部变量 全局变量在程序装入内存时就已经分
  • c++中内存堆栈的创建与回收

    c 43 43 new 堆 栈 根据32位的Windows系统默认有2GB的用户空间 xff0c 则不能new超过2GB的 xff0c 执行下列代码 xff1a double p 61 new double 128 1024 1024 2
  • c++中指针概念及指针变量的大小

    1 指针的概念 想要清楚的理解指针概念我们必须先弄清楚数据在内存中是怎样储存的 xff0c 又怎样读取的 在电脑的内存区里每一个字节都有一个编号 xff0c 这个编号就是地址 我们在程序中定义一个变量 xff0c 对程序进行编译时 xff0
  • c++中指针,堆栈内存分配重要概念理解汇总(实例注释)

    一个函数执行过程中堆栈分配情况实例详解 对于不同的平台程序 xff0c win32程序所有内存寻址 xff08 局部变量 xff0c 指针等 xff09 都是32位即4个字节 xff0c x64为64位8个字节 下面的例子可以完全展示不同的
  • Qt中使用QThread实现多线程2

    注意 xff1a 当一个QObject类型B gt MoveToThread xff08 线程A xff09 xff0c 必须执行线程A的Start方法才能启动线程 xff08 一个线程包含线程循环等 xff09 xff0c 这时如果想要将
  • Qt中一个信号连接多个槽函数后的执行顺序

    当我们想一个信号触发多个槽函数的时候 xff0c 又对执行顺序有要求 xff0c 要么要知道这些槽函数的执行顺序 xff0c Qt5后 xff0c 在信号发射后 xff0c 槽函数会按照链接顺序执行 xff0c 下面写个简单的例子来验证一下
  • QT中线程的依附性详解

    1 子线程中创建的对象不应再其他线程中被调用 xff0c 包括使用槽函数的形式 在创建了MyThread 对象后 xff0c obj otherObj yetAnotherObj 的线程依附性是怎么样的 xff1f 要回答这个问题 xff0
  • qt中使用connect指令来写socket但与QObject的connect冲突编译器报错问题解决

    经相关查阅 在connect前加个域分隔符就好了 connect 这样QT就不会误解了
  • webpack--加载器(loader)

    加载器loader webpack 只能理解 JavaScript 和 JSON 文件 xff0c 这是 webpack 开箱可用的自带能力 loader 让 webpack 能够去处理其他类型的文件 xff0c 并将它们转换为有效 模块
  • QT中多线程QThread使用解疑实例

    下面是测试类 xff1a define TESTDEMO H include 34 qdebug h 34 include 34 qthread h 34 include lt QObject gt class TestDemo publi
  • Qt中创建、写入、删除(INI、XML文件)

    include 34 widget h 34 include lt QApplication gt include lt QSettings gt include lt QDebug gt 写ini配置文件 void setIni QSet
  • Qt中的枚举类型的使用方法

    Qt中的枚举变量 Q ENUM Q FLAG Q NAMESPACE Q ENUM NS Q FLAG NS以及其他 前言 Q ENUM的使用 Q FLAG的引入解决什么问题 xff1f Q NAMESPACE Q ENUM NS和Q FL
  • QList类迭代器详解

    QList Detailed Description QList 是Qt的通用容器类之一 它将项目存储在一个列表中 xff0c 该列表提供了基于索引的快速访问以及基于索引的插入和删除 QList xff0c QLinkedList 和QVe
  • C++中返回引用和返回值的区别详解(1)

    一 主要讨论下面两个函数的区别 xff1a int amp at return m data int at return m data 上面两个函数 xff0c 第一个返回值是int的引用int amp xff0c 第二个返回值是int x