本篇博客,我们来看看,在C/C++里面函数的return 关键字究竟做了什么工作,我们从return 基本的数据类型 像int/char/void */,到带构造函数的类,一步步分析。

return int/char,void* 以及他们的引用···

#include <stdio.h>
#include <stdlib.h>
int bfunc()
{int rst = 0;return rst;
}void * bfunc2()
{void * p = NULL;return p;
}
char & bfun3()
{char ch = 0;return ch;
}
int main()
{int ret1 = bfunc();bfunc2();void * p = bfunc2();bfun3();char ch = bfun3();system("pause");
}

代码很简单,从int ret1=bfunc()开始,该函数返回一个基本类型int,我们来看看汇编结果:

    45:     int ret1 = bfunc();
00DD15EE  call        bfunc (0DD11FEh)
00DD15F3  mov         dword ptr [ret1],eax  

在00DD15EE 调用bfunc()
我们进入bfun():

    12: int bfunc()13: {
00DD1490  push        ebp
00DD1491  mov         ebp,esp
00DD1493  sub         esp,0CCh
00DD1499  push        ebx
00DD149A  push        esi
00DD149B  push        edi
00DD149C  lea         edi,[ebp-0CCh]
00DD14A2  mov         ecx,33h
00DD14A7  mov         eax,0CCCCCCCCh
00DD14AC  rep stos    dword ptr es:[edi]  14:     int rst = 0;
00DD14AE  mov         dword ptr [rst],0  15:     return rst;
00DD14B5  mov         eax,dword ptr [rst]  16: }
00DD14B8  pop         edi
00DD14B9  pop         esi
00DD14BA  pop         ebx
00DD14BB  mov         esp,ebp
00DD14BD  pop         ebp
00DD14BE  ret  

我们看最后的几步:

    15:     return rst;
00DD14B5  mov         eax,dword ptr [rst]  

可知 return rst是把局部变量rst的值保存在eax寄存器;
最后,函数调用完毕,执行

00DD15F3  mov         dword ptr [ret1],eax  

将刚刚寄存器eax里面的内容给[ret1].

这个例子相当简单,但是不妨碍它说明了函数return 一个基本数据类型的时候是先把返回结果放在eax这一规则。

ok,继续看。
接下来是

    bfunc2();void * p = bfunc2();

其中bfunc2()的返回值并没有保存下来,void *p =bfun2()则将返回值保存到p里面了。
我们看看汇编结果:

    47:     bfunc2();
00FA15F6  call        bfunc2 (0FA1208h)  48:     void * p = bfunc2();
00FA15FB  call        bfunc2 (0FA1208h)
00FA1600  mov         dword ptr [p],eax  

首先 bfun();和void * p=bfunc2()区别在于 是否有把eax的值mov到指定的内存。
然后 我们深入bfun():

   18: void * bfunc2()19: {
00FA1440  push        ebp
00FA1441  mov         ebp,esp
00FA1443  sub         esp,0CCh
00FA1449  push        ebx
00FA144A  push        esi
00FA144B  push        edi
00FA144C  lea         edi,[ebp-0CCh]
00FA1452  mov         ecx,33h
00FA1457  mov         eax,0CCCCCCCCh
00FA145C  rep stos    dword ptr es:[edi]  20:     void * p = NULL;
00FA145E  mov         dword ptr [p],0  21:     return p;
00FA1465  mov         eax,dword ptr [p]  22: }
00FA1468  pop         edi
00FA1469  pop         esi
00FA146A  pop         ebx
00FA146B  mov         esp,ebp
00FA146D  pop         ebp
00FA146E  ret  

与return int的类似,也是在return的时候把return 后面的表达式的值保存到eax寄存器中。

这是因为void * 指针 变量其实也是c/C++的基本数据类型。

ok,那我们看看返回引用的那个函数的调用:

    49:     bfun3();
00FA1603  call        bfun3 (0FA1203h)  50:     char ch = bfun3();
00FA1608  call        bfun3 (0FA1203h)
00FA160D  mov         al,byte ptr [eax]
00FA160F  mov         byte ptr [ch],al  

首先在调用上,char ch=bfun3()比bfunc3()多了

mov         al,byte ptr[eax];
mov         byte ptr [ch],al 

为什么是与al相关呢?这是因为 char是8位的,只需要al就可以了。
我们看内部:

    23: char & bfun3()24: {
00FA1560  push        ebp
00FA1561  mov         ebp,esp
00FA1563  sub         esp,0D0h
00FA1569  push        ebx
00FA156A  push        esi
00FA156B  push        edi
00FA156C  lea         edi,[ebp-0D0h]
00FA1572  mov         ecx,34h
00FA1577  mov         eax,0CCCCCCCCh
00FA157C  rep stos    dword ptr es:[edi]
00FA157E  mov         eax,dword ptr ds:[00FA8000h]
00FA1583  xor         eax,ebp
00FA1585  mov         dword ptr [ebp-4],eax  25:     char ch = 0;
00FA1588  mov         byte ptr [ch],0  26:     return ch;
00FA158C  lea         eax,[ch]  27: }
00FA158F  push        edx
00FA1590  mov         ecx,ebp
00FA1592  push        eax
00FA1593  lea         edx,ds:[0FA15B4h]
00FA1599  call        @_RTC_CheckStackVars@8 (0FA108Ch)
00FA159E  pop         eax
00FA159F  pop         edx
00FA15A0  pop         edi
00FA15A1  pop         esi
00FA15A2  pop         ebx
00FA15A3  mov         ecx,dword ptr [ebp-4]
00FA15A6  xor         ecx,ebp
00FA15A8  call        @__security_check_cookie@4 (0FA1023h)
00FA15AD  mov         esp,ebp
00FA15AF  pop         ebp
00FA15B0  ret  

哈哈 关键来啦:

    26:     return ch;
00FA158C  lea         eax,[ch]  

lea eax,[ch] ====>emmmmm,取ch的有效地址。
so,这下子应该心里有底了吧!返回引用的时候,其实是返回 ch变量的有效地址,而这个变量是个局部变量,该有效地址所指向的内存是有可能被下一次函数调用的函数栈帧破坏的,所以返回一个局部变量的引用可以说是很危险的。

返回地址 后那怎么搞嘞?

    50:     char ch = bfun3();
00FA1608  call        bfun3 (0FA1203h)
00FA160D  mov         al,byte ptr [eax]
00FA160F  mov         byte ptr [ch],al  

注意看:调用完 bfun3后,eax存着bfun3()函数里面ch的有效地址,接着首先通过mov al, byte ptr[eax],指令将eax的值视为一个byte类型的有效地址,取出这个地址下的内存的值,保存到al中,然后再将al的值mov到 main函数的ch变量中。

下面我们看一点高级的:

return struct/class,以及它们的引用

先看实例代码:

#include <stdio.h>
#include <stdlib.h>
#pragma pack(1)
struct Person
{int age;int id;int somethingelse;char name[12];void display(){printf("age :%X id:%X\n", age, id);printf("age:%X \n", &age);printf("id:%X \n", &id);printf("somethingelse:%X \n", &somethingelse);printf("name:%X \n", name);}Person(){printf("Call Person()\n");}Person(const Person&){printf("Call Person(const Person&)\n");}
};Person sfunc2()
{Person p ;return p;
}
Person sfunc3()
{int length = sizeof(Person);char * rst = new char[length];for (int i = 0; i < length; i++){rst[i] = 0xBB;}return *((Person*)(rst));
}
Person& sfunc4()
{Person* p = new Person();return *p;
}
int main()
{Person p;sfunc2().display();p = sfunc2();p.display();sfunc3().display();sfunc4().display();p = sfunc4();p.display();system("pause");return 0;
}

其中有一个Person的结构体,里面有4+4+4+12=24个字节的变量,以及定义了一个默认构造函数和复制构造函数。
然后分别有三个函数sfunc(),sfunc1(),sfunc2(),代表了返回栈对象,返回动态对象,返回动态对象的引用的情况。
main函数里面,获取返回值的也有不获取返回值的。

ok,我们先看sfunc2().display()的情况。
这个过程先调用sfunc2(),sfunc2()返回一个Person对象,然后再调用这个对象的display()方法。

    51:     sfunc2().display();
013F1890  lea         eax,[ebp-140h]
013F1896  push        eax
013F1897  call        sfunc2 (013F11E0h)
013F189C  add         esp,4
013F189F  mov         ecx,eax
013F18A1  call        Person::display (013F1046h)  

其过程为:
取[ebp-140h]的有效地址保存到eax,然后把eax压栈,然后再去调用sfunc2()。我们知道一般调用函数前的push的都是函数的参数,但是明明sfunc2()无须参数,那为什么会把ebp-140hpush进去嘞?我们接着看:
先记录一下刚刚压入的ebp-140H的值为0x00eff9f8,待会我们还会看到这个地址。
进入Person sfunc2()内部:

    28: Person sfunc2()29: {
013F1610  push        ebp
013F1611  mov         ebp,esp
013F1613  sub         esp,0E4h
013F1619  push        ebx
013F161A  push        esi
013F161B  push        edi
013F161C  lea         edi,[ebp-0E4h]
013F1622  mov         ecx,39h
013F1627  mov         eax,0CCCCCCCCh
013F162C  rep stos    dword ptr es:[edi]
013F162E  mov         eax,dword ptr ds:[013FA000h]
013F1633  xor         eax,ebp
013F1635  mov         dword ptr [ebp-4],eax  30:     Person p ;
013F1638  lea         ecx,[p]
013F163B  call        Person::Person (013F11FEh)  31:     return p;
013F1640  lea         eax,[p]
013F1643  push        eax
013F1644  mov         ecx,dword ptr [ebp+8]
013F1647  call        Person::Person (013F102Dh)
013F164C  mov         eax,dword ptr [ebp+8]  32: }
013F164F  push        edx
013F1650  mov         ecx,ebp
013F1652  push        eax
013F1653  lea         edx,ds:[13F1680h]  32: }
013F1659  call        @_RTC_CheckStackVars@8 (013F1096h)
013F165E  pop         eax
013F165F  pop         edx
013F1660  pop         edi
013F1661  pop         esi
013F1662  pop         ebx
013F1663  mov         ecx,dword ptr [ebp-4]
013F1666  xor         ecx,ebp
013F1668  call        @__security_check_cookie@4 (013F101Eh)
013F166D  add         esp,0E4h
013F1673  cmp         ebp,esp
013F1675  call        __RTC_CheckEsp (013F1154h)
013F167A  mov         esp,ebp
013F167C  pop         ebp
013F167D  ret  

重点看return p后面的代码:

   30:  Person p ;
013F1638  lea         ecx,[p]
013F163B  call        Person::Person (013F11FEh)  31:     return p;
013F1640  lea         eax,[p]
013F1643  push        eax
013F1644  mov         ecx,dword ptr [ebp+8]
013F1647  call        Person::Person (013F102Dh)
013F164C  mov         eax,dword ptr [ebp+8]  32: }

首先 取p的地址保存到eax寄存器,然后把eax的地址压栈。
接着取ebp+8的地址到ecx寄存器,我们都知道ebp+4是函数返回地址,而ebp+8其实是函数的传入参数,也就是在main函数中汇编代码压入的
0x00eff9f8
接下来call Person::Person(013F102Dh)

    22:     Person(const Person&)
013F1420  push        ebp
013F1421  mov         ebp,esp
013F1423  sub         esp,0CCh
013F1429  push        ebx
013F142A  push        esi
013F142B  push        edi
013F142C  push        ecx
013F142D  lea         edi,[ebp-0CCh]
013F1433  mov         ecx,33h
013F1438  mov         eax,0CCCCCCCCh
013F143D  rep stos    dword ptr es:[edi]
013F143F  pop         ecx
013F1440  mov         dword ptr [this],ecx  23:     {24:         printf("Call Person(const Person&)\n");
013F1443  mov         esi,esp
013F1445  push        13F78BCh
013F144A  call        dword ptr ds:[13FB118h]
013F1450  add         esp,4
013F1453  cmp         esi,esp
013F1455  call        __RTC_CheckEsp (013F1154h)  25:     }
013F145A  mov         eax,dword ptr [this]
013F145D  pop         edi
013F145E  pop         esi
013F145F  pop         ebx
013F1460  add         esp,0CCh
013F1466  cmp         ebp,esp
013F1468  call        __RTC_CheckEsp (013F1154h)
013F146D  mov         esp,ebp
013F146F  pop         ebp
013F1470  ret         4  

关键代码在:


013F142C  push        ecx
013F142D  lea         edi,[ebp-0CCh]
013F1433  mov         ecx,33h
013F1438  mov         eax,0CCCCCCCCh
013F143D  rep stos    dword ptr es:[edi]
013F143F  pop         ecx
013F1440  mov         dword ptr [this],ecx  

首先把ecx的值压入栈顶,后面又给弹回来到ecx寄存器,而此时这个ecx的值其实就是0x00eff9f8,
然后就是 013F1440 mov dword ptr [this],ecx 这一句揭示了ecx的0x00eff9f8其实就当前构造函数的this指针。

我们回想一下0x00eff9f8这个地址其实是在刚刚main()函数栈帧里面的某一个地址,因为调用的是返回对象的函数,所以编译器会先在调用这个函数(在上面的例子中是main函数)的所在的函数栈上搞出一个匿名的对象出来,并将这个对象的指针动作这个返回对象函数(如sfunc2())的一个参数压入栈中。在sfunc2()函数的return的时候,就要调用这个调用这个匿名对象的复制构造函数将匿名对象的各成员进行初始化或者赋值。

我们再看sfunc2()在调用完这个复制构造函数后:

    31:     return p;
013F1640  lea         eax,[p]
013F1643  push        eax
013F1644  mov         ecx,dword ptr [ebp+8]
013F1647  call        Person::Person (013F102Dh)
013F164C  mov         eax,dword ptr [ebp+8]  32: }

mov eax, dword ptr [ebp+8]
将匿名对象的地址放到eax寄存。这样sfunc2()就执行完了,接着:

    51:     sfunc2().display();
013F1890  lea         eax,[ebp-140h]
013F1896  push        eax
013F1897  call        sfunc2 (013F11E0h)
013F189C  add         esp,4
013F189F  mov         ecx,eax
013F18A1  call        Person::display (013F1046h)  

将匿名对象的地址由eax存到ecx,再调用display()函数,在display函数中通过ecx寄存可以知道当前display内部的this指针是0x00eff9f8

013F189F  mov         ecx,eax
013F18A1  call        Person::display (013F1046h)  。

return 栈对象的流程为:
1。在调用返回一个对象的函数前,会在当前的函数所在的作用域生成一块栈内存区域用于存储一个匿名的对象,这个对象没有调用过构造函数。编译器将这个匿名对象的内存首地址作为一个参数压入栈顶,共该返回对象的函数使用。
2。return的时候,用return 后面表达式的对象作为参数来调用 1 中的匿名对象的复制构造函数。
3。把匿名对象的地址存入eax
4。ret 返回原函数。

然后原函数通过eax里面的匿名对象的指针来获取这个对象的数据。

    p = sfunc2();p.display();

比sfunc2().display()多了一步将函数调用后eax所指向的匿名对象赋值给p的过程。其他一样。
sfunc2()与sfunc3()一样,
但是sfunc4()有点特殊。
调用过程:

    55:     sfunc4().display();
013F18F6  call        sfunc4 (013F1082h)
013F18FB  mov         ecx,eax
013F18FD  call        Person::display (013F1046h)  

没有像上面返回对象的函数那样将 某个匿名对象的地址压栈。
函数内部:

    43: Person& sfunc4()44: {
013F1770  push        ebp
013F1771  mov         ebp,esp
013F1773  push        0FFFFFFFFh
013F1775  push        13F512Eh
013F177A  mov         eax,dword ptr fs:[00000000h]
013F1780  push        eax
013F1781  sub         esp,0E8h
013F1787  push        ebx
013F1788  push        esi
013F1789  push        edi
013F178A  lea         edi,[ebp-0F4h]
013F1790  mov         ecx,3Ah
013F1795  mov         eax,0CCCCCCCCh
013F179A  rep stos    dword ptr es:[edi]
013F179C  mov         eax,dword ptr ds:[013FA000h]
013F17A1  xor         eax,ebp
013F17A3  push        eax
013F17A4  lea         eax,[ebp-0Ch]
013F17A7  mov         dword ptr fs:[00000000h],eax  45:     Person* p = new Person();
013F17AD  push        18h
013F17AF  call        operator new (013F11A4h)
013F17B4  add         esp,4
013F17B7  mov         dword ptr [ebp-0E0h],eax
013F17BD  mov         dword ptr [ebp-4],0
013F17C4  cmp         dword ptr [ebp-0E0h],0
013F17CB  je          sfunc4+70h (013F17E0h)
013F17CD  mov         ecx,dword ptr [ebp-0E0h]
013F17D3  call        Person::Person (013F11FEh)
013F17D8  mov         dword ptr [ebp-0F4h],eax
013F17DE  jmp         sfunc4+7Ah (013F17EAh)
013F17E0  mov         dword ptr [ebp-0F4h],0
013F17EA  mov         eax,dword ptr [ebp-0F4h]
013F17F0  mov         dword ptr [ebp-0ECh],eax
013F17F6  mov         dword ptr [ebp-4],0FFFFFFFFh
013F17FD  mov         ecx,dword ptr [ebp-0ECh]
013F1803  mov         dword ptr [p],ecx  46:     return *p;
013F1806  mov         eax,dword ptr [p]  47: }

它没有调用匿名对象的复制构造函数,(因为它根本不需要匿名对象了),而是直接将p的有效地址保存到eax.
这也就解释了为什么 函数返回对象的引用一般比返回对象更高效一些:

返回引用无匿名对象产生,无须调用匿名对象的构造函数,自然也就没有匿名对象的析构问题。

总结

1.return 基本数据类型(char,int,void*,float,double)是,将中间结果保存至eax,调用者通过访问eax寄存器来获取函数返回结果。

2.返回引用其实返回的是被引用的对象的地址

3.A函数调用返回对象的函数F时,会在A的栈帧生成一个匿名对象,然后再F return的时候调用这个匿名对象的复制构造函数将return 表达式里面对象的成员变量的值传送到匿名对象中。

4.A函数调用返回对象引用的函数F时,F会将待引用的地址返回,无匿名对象,无匿名对象的复制构造函数一说。

C/C++ return 如何实现的?return 的内部机制相关推荐

  1. 你真的了解try{ return }finally{}中的return?

    你真的了解try{ return }finally{}中的return? 今天去逛论坛 时发现了一个很有趣的问题: 谁能给我我解释一下这段程序的结果为什么是:2.而不是:3 代码如下: class T ...

  2. servlet中实现页面跳转return “r:”和return “f:

    servlet中实现页面跳转return "r:"和return "f:"的区别和作用 2015-07-28 14:22741830480 | 浏览 48 次 ...

  3. python返回值return用法_Python中return函数返回值代码实例用法

    本篇文章小编给大家分享一下Python中return函数返回值代码实例用法,文章代码介绍的很详细,小编觉得挺不错的,现在分享给大家供大家参考,有需要的小伙伴们可以来看看. return 添加返回值 r ...

  4. 'main' : function should return a value; 'void' return type assumed/////undeclared identifier

    'main' : function should return a value; 'void' return type assumed 'c' : undeclared identifier 在调试c ...

  5. python3 return用法_Python中return语句用法实例分析

    本文实例讲述了Python中return语句用法.分享给大家供大家参考.具体如下: return语句: return语句用来从一个函数 返回 即跳出函数.我们也可选从函数 返回一个值 . 使用字面意义 ...

  6. python if return语句_Python: return语句

    人生苦短,我用Python 环境:Windows 10 64-bit, python == 3.6.4 , PyCharm CE == 2018.1 声明:学习资源来自于网络,这里是自己学习笔记总结与 ...

  7. python中return的理解-Python return语句 函数返回值

    return语句是从python 函数返回一个值,在讲到定义函数的时候有讲过,每个函数都要有一个返回值.Python中的return语句有什么作用,今天就来仔细的讲解一下. python 函数返回值 ...

  8. python if写在return 后面_python中return如何写

    python中return的用法 1.return语句就是把执行结果返回到调用的地方,并把程序的控制权一起返回 程序运行到所遇到的第一个return即返回(退出def块),不会再运行第二个return ...

  9. 云漫圈 | finally到底是在return之前执行还是return之后执行?

    戳蓝字"CSDN云计算"关注我们哦! 文章来自:程序员乔戈里作者:乔戈里qgl --下课后-- public class Main { public static void mai ...

最新文章

  1. windows下的cmd命令(全面)
  2. python强制可读吗_python 中的强制类型转换
  3. CSS+js弹出居中的背景半透明div层
  4. 【Paper】2020_GrHDP Solution for Optimal Consensus Control of Multiagent Discrete-Time Systems
  5. 'webpack-dev-server' 不是内部或外部命令,也不是可运行的程序 或批处理文件的解决方法(webpack热加载)
  6. int 转interger java_Java中Integer和int之间的转换
  7. java加载配置文件
  8. 淘富成真,硬件智能—— 硬件创新一站赋能平台
  9. 从零开始学Android自定义View之动画系列——属性动画(2)
  10. 大一线性代数知识点总结
  11. 虚拟机桥接模式下和windows相互ping通
  12. 深信服 AC上网 行为管理设置
  13. 一:计算机基础入门及介绍
  14. vue实现lodop打印功能
  15. 安装kubernetes k8s v1.16.0 国内环境
  16. Java调用的高德api,通过经纬度查询地址信息 - 记录
  17. position与清除浮动
  18. 罗格斯的计算机科学博士奖学金,罗格斯大学cs怎么样
  19. git版本控制gitosis的安装与使用
  20. 李沐动手学深度学习v2-目标检测中的锚框和代码实现

热门文章

  1. WPS JS宏示例——工作表排序
  2. 【嵌入式Linux学习笔记】Linux驱动开发
  3. TSQL与PL/SQL的比较(不完全版)
  4. 计算机中的三类总线是什么,计算机中三总线是什么意思
  5. flask_session中配置项SESSION_PERMANENT 时候会自动刷新过期时间
  6. 涂鸦LZ201-CN开发板学习笔记(一)
  7. 公链分析报告(2)--EOS
  8. 学生管理系统【Python】
  9. arduino液晶显示屏与温湿度传感器连接
  10. Create database/Create table 示例(Sql Server2005语法)