以前一直挺好奇的,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. SSE图像算法优化系列十二:多尺度的图像细节提升。

    无意中浏览一篇文章,中间提到了基于多尺度的图像的细节提升算法,尝试了一下,还是有一定的效果的,结合最近一直研究的SSE优化,把算法的步骤和优化过程分享给大家. 论文的全名是DARK IMAGE ENH ...

  2. java 之 工厂模式(大话设计模式)

    在以前的文章里面讲述过简单工厂模式,见链接:http://www.cnblogs.com/zhuxiansheng/p/7873161.html 简单工厂模式解耦了客户端和实现的依赖,不过如果有再次扩 ...

  3. Nvidia TX2 Robot 环境配置记录

    p.p1 { margin: 0.0px 0.0px 2.0px 0.0px; font: 14.0px "Helvetica Neue"; color: #454545 } p. ...

  4. mongodb数据库禁止外网访问以及添加账号

    未曾料到被黑客勒索比特币的戏码竟然降临到我的身上,几个月的技术积累付之一炬.怪只怪自己学艺不精,心存侥幸和无知,不过经此一役,方知网络安全防护的重要性. 一直未给自己的mongodb数据库设置账号密码 ...

  5. Android开发过程中使用弱引用解决内存泄露的习惯

    Java虽然有垃圾回收,但是仍然存在内存泄露,比如静态变量.缓存或其他长生命周期的对象引用了其他对象,这些被引用的对象就会长期不能被GC释放,导致内存泄露. 弱引用(WeakReference)是解决 ...

  6. ajax处理级联访问数据库显示

    首先创建简单的html页面,写出如下代码: 静态页相关代码: js代码:

  7. js脚本根据身份证号获取性别、年龄、家庭地址、生日

    做项目测试时需要根据身份证号获取其信息,也不想调接口,就自己在本地通过收集资料整合了一个

  8. linux grep 从入门到精通

    linux grep 从入门到精通 一.初级 搜索日志 grep "186" catalina.out 在新输出日志中监听固定字符串 tail -f catalina.out | ...

  9. mysql建表测试

    drop table if exists news; --如果存在表则删除create table news  --创建表(        id int unsigned not null auto_ ...

  10. WeChat 隐私政策

    隐私政策 本应用尊重并保护所有使用服务用户的个人隐私权.为了给您提供更准确.更有个性化的服务,本应用会按照本隐私权政策的规定使用和披露您的个人信息.但本应用将以高度的勤勉.审慎义务对待这些信息.除本隐 ...