关于C++函数返回局部对象的详细分析

本文详细解析了C++中如何在函数内返回一个局部对象的过程。通过具体的代码实例及对应的汇编指令解释,展示了VS编译器如何处理这一过程,包括在栈中分配内存、复制对象数据以及返回内存地址等关键步骤。

以前一直挺好奇的,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;
}



转载于:https://www.cnblogs.com/ysherlock/p/7822287.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值