以前一直挺好奇的,C++是怎么在函数内返回一个局部对象的。因为按照我之前的想法,函数返回一个基本类型的值是通过存放到ecx实现的(关于浮点不了解),但是局部对象又是比较大的,很明显不能使用寄存器作为通用解决方案,虽然也能猜想到可能是用函数栈实现的,但是具体如何没了解过,今天偶有闲时兴趣正浓仔细看了一遍汇编大概了解了 VS编译器对于函数返回局部对象的处理方法, 这里分享出来与君共勉。

代码非常简单,首先定义一个对象,然后定义一个函数返回一个局部对象,最后主函数调用该函数

class ReturnAnObject {
public:
int arr[10];
int num;
}; ReturnAnObject returnAnObjectFunc() {
ReturnAnObject obj;
obj.num = 0x12345678;
for (int i = 0; i < 10; i++) {
obj.arr[i] = i + 3;
}
return obj;
} int main() {
ReturnAnObject obj;
obj = returnAnObjectFunc();
return 0;
}

函数中for循环主要是为了防止报错,为了突出主题,关于设置栈帧和循环的部分就省略了

returnAnObjectFunc:
ReturnAnObject obj;
obj.num = 0x12345678;
for (int i = 0; i < 10; i++) {
obj.arr[i] = i + 3;
}
return obj;
;;;;这里开始正题,首先设置ecx为0Bh用以后面的循环
mov ecx,0Bh
;;;;然后获取obj的地址存放到esi
lea esi,[obj]
;;;;注意这里[ebp+8],很熟悉吧,以前实际参数也是这样获取的
;;;;这里一样,所以我们可以猜想这个函数把局部对象存放到参数上面
;;;;后面如果我们能看到调用这个函数如果还压入其他数据就能肯定我们的猜想
;;;;(因为这个函数是没有参数的)
mov edi,dword ptr [ebp+8]
;;;;重复把esi的数据复制到es:[edi],esi会自动向前移动,重复次数为ecx值
rep movs dword ptr es:[edi],dword ptr [esi]
;;;;顺着我们之前的猜想我们可以进一步认为这个函数已经把局部变量复制到了参数上
;;;;然后返回参数地址相当于返回变量
mov eax,dword ptr [ebp+8]

然后看main函数

main:
push ebp
mov ebp,esp
sub esp,15Ch
push ebx
push esi
push edi
lea edi,[ebp-15Ch]
mov ecx,57h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
ReturnAnObject obj;
obj = returnAnObjectFunc();
;;;;注意下面三行代码,这里取main函数栈里面一块很大空间(不全是用于存放函数返回的局部变量)的首地址作为参数压栈
;;;;但是returnAnObjectFunc是没有参数的,而且也不是类成员函数,不存在this的可能
;;;;很明显我们的猜想是正确的,这块内存就用来存放返回的局部变量
lea eax,[ebp-158h]
push eax
call returnAnObjectFunc (039B16Fh)
;;;;平衡栈
add esp,4
;;;;这里首先设置循环次数
mov ecx,0Bh
;;;;然后把之前returnAnObjectFunc参数地址复制到esi
mov esi,eax
;;;;[ebp-124h]就是当前main函数中局部临时变量的地址
lea edi,[ebp-124h]
;;;;重复执行复制之前保存的局部变量到当前局部临时变量
rep movs dword ptr es:[edi],dword ptr [esi]
;;;;最后下面这些操作把当前局部临时变量复制到当前局部变量obj出
mov ecx,0Bh
lea esi,[ebp-124h]
lea edi,[obj]
rep movs dword ptr es:[edi],dword ptr [esi]
return 0;
;;;;eax清零
xor eax,eax

至此在Debug模式下返回被掉函数局部对象然后赋值给当前调用函数局部变量就完成了,我们可以总结一下:
首先调用函数会在栈内开辟一段内存用来保存被调函数的局部变量,然后把这段内存的首地址压栈并调用函数,
进入被调函数,被调函数会将局部变量复制到压入的参数的那片内存,然后再返回那片内存的首地址
其实到这里局部变量的返回已经结束了,为了加深印象我们在main创建obj然后调用returnAnObjectFunc给它赋值,具体体现到汇编代码就是
在main函数栈中创建一个临时变量然后把returnAnObjectFunc返回的那片内存(通过返回的首地址访问)复制到这个临时变量,再把临时变量复制给当前的局部变量obj
可以改出一段伪代码模拟这段汇编:

void* returnAnObjectFunc(void * address) {
ReturnAnObject obj;
obj.num = 0x12345678;
for (int i = 0; i < 10; i++) {
obj.arr[i] = i + 3;
} copyObjectData(address,&obj);
return address;
} int main() {
void* newMem = mallocSuitableMemory();
ReturnAnObject obj;
newMem = returnAnObjectFunc(newMem);
ReturnAnObject temp;
copyObjectData(&temp,newMem);
obj=temp;
return 0;
}

关于C++函数返回局部对象的详细分析的更多相关文章

  1. 条款31: 千万不要返回局部对象的引用,也不要返回函数内部用new初始化的指针的引用

    先看第一种情况:返回一个局部对象的引用.它的问题在于,局部对象 ----- 顾名思义 ---- 仅仅是局部的.也就是说,局部对象是在被定义时创建,在离开生命空间时被销毁的.所谓生命空间,是指它们所在的 ...

  2. C++ //拷贝构造函数调用时机//1.使用一个已经创建完毕的对象来初始化一个新对象 //2.值传递的方式给函数参数传值 //3.值方式返回局部对象

    1 //拷贝构造函数调用时机 2 3 4 #include <iostream> 5 using namespace std; 6 7 //1.使用一个已经创建完毕的对象来初始化一个新对象 ...

  3. c/c++不能返回局部对象和局部变量的指针或引用解释

    在编写c/c++代码时,调用函数的书写让程序变得整洁易读,但是调用函数的返回值(局部变量的返回值,变量,结构体,数组等)也有注意事项.c/c++严禁返回局部变量的指针或引用. 其实函数的返回值的规则非 ...

  4. C++函数返回局部指针变量

    遇到过好几次关于函数返回指针变量问题,有时候是可以的,有时候是不可以的,然后就混乱了.今天研究了下,结果发现原来和内存分配有关. 用下面的例子分析下吧: char * test() { char a[ ...

  5. 【C++对象模型】函数返回C++对象的问题

    在深入C++对象模型中,对于形如 CObj obj1 = Get(obj2); 的形式,编译器会在将其改变为如下 Get(obj, CObj&  obj1); 将赋值操作符左边的变量作为函数的 ...

  6. 类1(this指针/const成员函数/类作用域/外部成员函数/返回this对象的函数)

    假设我们要设计一个包含以下操作的 Sales_data 类: 1.一个 isbn 成员函数,用于返回对象的 book_no 成员变量 2.一个 combine 成员函数,用于将一个 Sales_dat ...

  7. 【Vue】定义组件 data 必须是一个函数返回的对象

    Vue 实例的数据对象.Vue 将会递归将 data 的属性转换为 getter/setter,从而让 data 的属性能够响应数据变化.对象必须是纯粹的对象 (含有零个或多个的 key/value ...

  8. Python3基础 Python的函数都有返回值 无指定返回值的函数 返回NONE对象

    镇场诗: 诚听如来语,顿舍世间名与利.愿做地藏徒,广演是经阎浮提. 愿尽吾所学,成就一良心博客.愿诸后来人,重现智慧清净体.-------------------------------------- ...

  9. 函数返回new对象

    #include <iostream> using namespace std; // foo()函数本质上没什么问题,但建议你不要这样写代码 string &foo() { st ...

随机推荐

  1. 【转】Java中用单例模式有什么好处

    Java Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在. 使用Singleton的好处还在于可以节省内存,因为它限制了实例的个数,有利于Java垃圾回收( ...

  2. 基于 HTML5 WebGL 的 3D 仓储管理系统

    仓储管理系统(WMS)是一个实时的计算机软件系统,它能够按照运作的业务规则和运算法则,对信息.资源.行为.存货和分销运作进行更完美地管理,使其最大化满足有效产出和精确性的要求.从财务软件.进销存软件C ...

  3. 为并发而生的 ConcurrentHashMap(Java 8)

    HashMap 是我们日常最常见的一种容器,它以键值对的形式完成对数据的存储,但众所周知,它在高并发的情境下是不安全的.尤其是在 jdk 1.8 之前,rehash 的过程中采用头插法转移结点,高并发 ...

  4. 基于TCP协议的socket编程

    什么是socket Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面, ...

  5. mac下出现xcrun: error导致git、svn无法使用的解决办法

    现象:xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun ...

  6. UVA 1426 - Discrete Square Roots(数论)

    UVA 1426 - Discrete Square Roots 题目链接 题意:给定X, N. R.要求r2≡x (mod n) (1 <= r < n)的全部解.R为一个已知解 思路: ...

  7. x86内存映射

    Contents 1 "Low" memory (< 1 MiB) 1.1 Overview 1.2 BIOS Data Area (BDA) 1.3 Extended BI ...

  8. jQuery 学习笔记(三)——事件与应用

    页面载入时触发ready()事件 ready()事件类似于onLoad()事件.但前者仅仅要页面的DOM结构载入后便触发.而后者必须在页面所有元素载入成功才触发,ready()能够写多个,按顺序运行. ...

  9. startup alter.log spfile.ora

    SQL> select * from v$version where rownum=1; BANNER --------------------------------------------- ...

  10. 游戏AI(二)—行为树优化之

    上一篇我们讲到了AI架构之一的行为树,本篇文章和下一篇文章我们将对行为树进行优化,在本篇文章中我们讲到的是内存优化 问题 上一篇中我们设计的行为树由于直接采用new进行动态内存分配,没有自己进行管理. ...