c++返回值

这几天一直在给同学讲数据结构和算法的题,讲题的过程中突然想到这函数的返回值究竟是存放在哪的,为什么可以通过函数的调用来直接给变量赋值。仔细想想这 么多年居然没有深究过这个问题,太不应该,于是今天花了些时间来好好研究了一下这个问题。也跟了一些thread,某些前辈的观点是“不应该动不动就提到 寄存器,栈,堆这些较底层的概念。完全可以这样理解:返回值保存在一段临时区域,到下一条语句时,返回值被销毁,所以,如果要继续使用返回值,必须将返回 值赋予其他变量。”这确实不假,但是我觉得这位前辈应该是在透彻理解了内在的机理之后才这么说的,我觉得作为学习过程还是应该深究一下。 

另有一位前辈说“不大于32位的(返回值)在eax中,64位的在edx + eax中。再大就传指针了。” 

那么就拿反汇编代码开刀,验证一下。 

(一)  基础知识 

在看反汇编代码之前,应该有几点要明确一下: 

1. 每个进程对应一个Call Stack(调用栈),而CallStack 又由许多Stack Frame(栈帧)组成,对应每个未完成的函数调用,由系 统分配。(VC调试中默认是会显示调用栈窗口的,越往栈顶的就是越最近调用的函数,黑色显示的就是还未返回的函数调用) 

2.eax, ebx, ecx, edx, esi, edi, ebp, esp等都是X86 汇编语言中CPU上的32位通用寄存器的名称,他们各自的作用如下: 

EAX 是累加器(accumulator), 它是很多加法乘法指令的缺省寄存器。 

EBX 是基地址(base)寄存器,在内存寻址时存放基地址。 

ECX 是计数器(counter),是重复(REP)前缀指令和LOOP指令的内定计数器。 

EDX 则一般被用来放整数除法产生的余数。 

ESI/EDI分别是源/目标索引寄存器(source/destinationindex),在很多字符串操作指令中,DS:ESI指向源串,而ES:EDI指向目标串。 

EBP是基址指针(BASE POINTER),按照习惯,通常用于界定StackFrame(栈帧)。 

ESP 是堆栈指针,被形象地称为栈顶指针,堆栈的顶部是地址小的区域,压入堆栈的数据越多,ESP也就越来越小。在32位平台上,每压入一个数据,ESP就减少4字节。 

3.windows平台下的Call Stack,从上(高地址)往下(低地址)生长。 

(二)代码分析 

首先我实验的平台是32位版vs2010,程序为Debug版本。 

先试试32位整型的int,C++的实验代码如下: 


[cpp] view plaincopyprint?#include "stdafx.h"    
int minus(int a ,int b)   
{   
         int j=2;   
         return a-b;   
}   
int _tmain(int argc, _TCHAR* argv[])   
{   
         int i=1;   
         int c=minus(3,1);   
         return 0;   
}   

#include "stdafx.h" 
int minus(int a ,int b) 

         int j=2; 
         return a-b; 

int _tmain(int argc, _TCHAR* argv[]) 

         int i=1; 
         int c=minus(3,1); 
         return 0; 



在return 0处设置断点,或者直接用system(“pause”)也行。 

调试运行,可以看到反汇编代码如下: 


[cpp] view plaincopyprint?int minus(int a ,int b)   
{   
/*step8 再次将EPB压栈,并将ESP的值放入EPB中,此时ESP指向位置5,EPB也指向位置5,这个位置也正是minus()栈帧的底部,同时又是Main()栈帧的顶部*/   
01091380 push        ebp    
01091381 mov         ebp,esp    
/*step9 作用同step2。此时EPB位置不变,ESP指向位置6*/   
01091383 sub         esp,0CCh    
/*step10 作用同step3。此时EPB位置不变,ESP指向位置7*/   
01091389 push        ebx    
0109138A push        esi    
0109138B push        edi    
/*step11 作用同step4。ESP和EPB位置不变*/   
0109138C lea         edi,[ebp-0CCh]    
01091392 mov         ecx,33h    
01091397 mov         eax,0CCCCCCCCh    
0109139C rep stos    dword ptres:[edi]    
         int j=2;   
/*step12 作用同step5。ESP和EPB位置不变*/   
0109139E mov         dword ptr [j],2    
         return a-b;   
/*step13 将参数a的值放入EAX。然后将参数b的值也传入,在EAX中进行减法,结果存放在EXA中。ESP和EPB位置不变*/   
010913A5 mov         eax,dword ptr [a]    
010913A8 sub         eax,dword ptr [b]    
}   
/*step14 返 回过程。将三个寄存器的值弹出,恢复到调用前的状态。并将EBP的值赋给ESP,此时EBP与ESP都指向位置5,也就是释放了从位置6到位置5的预留空 间。最后弹出EBP的值,使其恢复到调用minus()前的状态,ret语句进行返回,返回到先前压入栈中的返回地址所指向的地址处。此时EPB指向位置 0,ESP指向位置3*/   
010913AB pop         edi    
010913AC pop         esi    
010913AD pop         ebx    
010913AE mov         esp,ebp    
010913B0 pop         ebp    
010913B1 ret   
   
int _tmain(int argc, _TCHAR* argv[])   
{   
/*step1 运 行时库的启动函数mainCRTStartup()调用Main(),正式进入主函数,将EPB压栈,再将ESP的值放入EPB中,此时ESP指向位置 0,EPB也指向位置0,这个位置也正是的Main()栈帧的底部,同时又是mainCRTStartup()栈帧的顶部*/   
010913C0 push        ebp    
010913C1 mov         ebp,esp    
/*step2 将ESP下移0CCh个字节,也就是位置0到位置1之间的空间,这段空间将预留用来存储局部变量,此时ESP指向位置1,EPB指向位置0*/   
010913C3 sub         esp,0D8h    
/*step3 将三个寄存器的值压栈,此时ESP指向位置2,EPB指向位置0*/   
010913C9 push        ebx    
010913CA push        esi    
010913CB push        edi    
/*step4 首 先将位置1的地址放入目标索引寄存器EDI中,stos dwordptr es:[edi]的意思是从累加器EAX传送4个字节(双字),到由EDI作 为指针的目的串中,同时修改EDI以指向串中的下一个单元,前面加上rep代表重复执行,执行次数就是ECX寄存器中的值,而36h*4正好等于 0D8h,所以以下4句的作用就是将刚刚预留的位置0到位置1之间的空间全部赋值为0CCCCCCCCh ,这其实就是int 3断点,以防止意外访问。 ESP和EPB位置不变*/   
010913CC lea         edi,[ebp-0D8h]    
010913D2 mov         ecx,36h    
010913D7 mov         eax,0CCCCCCCCh    
010913DC rep stos    dword ptres:[edi]    
         int i=1;   
/*step5 将1传到局部变量i所在地址中,也就是刚刚预留的那块空间中,i应当是EBP-偏移量的形式。ESP和EPB位置不变*/   
010913DE mov         dword ptr [i],1    
         int c=minus(3,1);   
/*step6 在调用minus()前,按从右到左的顺序将实参压栈。EPB位置不变,ESP指向位置3*/   
010913E5 push        1    
010913E7 push        3    
/*step7 调用minus(),并将下一条语句的地址010913EE压栈,作为返回地址。EPB位置不变,ESP指向位置4*/   
010913E9 call        minus (10911C7h)    
/*step15 从 minus返回。将ESP+8,代表释放前面压入栈中的两个市场(2个4字节),并将EAX中的值(也就是返回值)赋给局部变量c,这里也就证明了当返回 值是32位int类型的时候,返回值确实是暂存在EAX中的。EPB指向位置0,ESP指向位置2*/   
010913EE add         esp,8    
010913F1 mov         dword ptr [c],eax    
         return 0;   
/*step16 准 备返回0给main函数。先对EAX自身做异或运算,EAX变成0,此时返回值才是真正的不存在了。然后将三个寄存器的值弹出,恢复到 mainCRTStartup()调用main()前的状态,接着释放位置1到位置0之间的预留空间。EPB和ESP都指向位置0。再接着通过比较EBP 与ESP的值,检查ESP是否正常。不相等则调用ILT+305指向的异常处理函数,使ESP等于EBP。正常的话就直接弹出EBP,结束main()函 数,返回mainCRTStartup()函数中继续剩下的操作。*/   
010913F4 xor         eax,eax    
}   
010913F6 pop         edi    
010913F7 pop         esi    
010913F8 pop         ebx    
010913F9 add         esp,0D8h    
010913FF cmp         ebp,esp    
01091401 call       @ILT+305(__RTC_CheckEsp) (1091136h)    
01091406 mov         esp,ebp    
01091408 pop         ebp    
01091409 ret     

int minus(int a ,int b) 

/*step8 再次将EPB压栈,并将ESP的值放入EPB中,此时ESP指向位置5,EPB也指向位置5,这个位置也正是minus()栈帧的底部,同时又是Main()栈帧的顶部*/ 
01091380 push        ebp  
01091381 mov         ebp,esp  
/*step9 作用同step2。此时EPB位置不变,ESP指向位置6*/ 
01091383 sub         esp,0CCh  
/*step10 作用同step3。此时EPB位置不变,ESP指向位置7*/ 
01091389 push        ebx  
0109138A push        esi  
0109138B push        edi  
/*step11 作用同step4。ESP和EPB位置不变*/ 
0109138C lea         edi,[ebp-0CCh]  
01091392 mov         ecx,33h  
01091397 mov         eax,0CCCCCCCCh  
0109139C rep stos    dword ptres:[edi]  
         int j=2; 
/*step12 作用同step5。ESP和EPB位置不变*/ 
0109139E mov         dword ptr [j],2  
         return a-b; 
/*step13 将参数a的值放入EAX。然后将参数b的值也传入,在EAX中进行减法,结果存放在EXA中。ESP和EPB位置不变*/ 
010913A5 mov         eax,dword ptr [a]  
010913A8 sub         eax,dword ptr [b]  

/*step14 返 回过程。将三个寄存器的值弹出,恢复到调用前的状态。并将EBP的值赋给ESP,此时EBP与ESP都指向位置5,也就是释放了从位置6到位置5的预留空 间。最后弹出EBP的值,使其恢复到调用minus()前的状态,ret语句进行返回,返回到先前压入栈中的返回地址所指向的地址处。此时EPB指向位置 0,ESP指向位置3*/ 
010913AB pop         edi  
010913AC pop         esi  
010913AD pop         ebx  
010913AE mov         esp,ebp  
010913B0 pop         ebp  
010913B1 ret 

int _tmain(int argc, _TCHAR* argv[]) 

/*step1 运 行时库的启动函数mainCRTStartup()调用Main(),正式进入主函数,将EPB压栈,再将ESP的值放入EPB中,此时ESP指向位置 0,EPB也指向位置0,这个位置也正是的Main()栈帧的底部,同时又是mainCRTStartup()栈帧的顶部*/ 
010913C0 push        ebp  
010913C1 mov         ebp,esp  
/*step2 将ESP下移0CCh个字节,也就是位置0到位置1之间的空间,这段空间将预留用来存储局部变量,此时ESP指向位置1,EPB指向位置0*/ 
010913C3 sub         esp,0D8h  
/*step3 将三个寄存器的值压栈,此时ESP指向位置2,EPB指向位置0*/ 
010913C9 push        ebx  
010913CA push        esi  
010913CB push        edi  
/*step4 首 先将位置1的地址放入目标索引寄存器EDI中,stos dwordptr es:[edi]的意思是从累加器EAX传送4个字节(双字),到由EDI作 为指针的目的串中,同时修改EDI以指向串中的下一个单元,前面加上rep代表重复执行,执行次数就是ECX寄存器中的值,而36h*4正好等于 0D8h,所以以下4句的作用就是将刚刚预留的位置0到位置1之间的空间全部赋值为0CCCCCCCCh ,这其实就是int 3断点,以防止意外访问。 ESP和EPB位置不变*/ 
010913CC lea         edi,[ebp-0D8h]  
010913D2 mov         ecx,36h  
010913D7 mov         eax,0CCCCCCCCh  
010913DC rep stos    dword ptres:[edi]  
         int i=1; 
/*step5 将1传到局部变量i所在地址中,也就是刚刚预留的那块空间中,i应当是EBP-偏移量的形式。ESP和EPB位置不变*/ 
010913DE mov         dword ptr [i],1  
         int c=minus(3,1); 
/*step6 在调用minus()前,按从右到左的顺序将实参压栈。EPB位置不变,ESP指向位置3*/ 
010913E5 push        1  
010913E7 push        3  
/*step7 调用minus(),并将下一条语句的地址010913EE压栈,作为返回地址。EPB位置不变,ESP指向位置4*/ 
010913E9 call        minus (10911C7h)  
/*step15 从 minus返回。将ESP+8,代表释放前面压入栈中的两个市场(2个4字节),并将EAX中的值(也就是返回值)赋给局部变量c,这里也就证明了当返回 值是32位int类型的时候,返回值确实是暂存在EAX中的。EPB指向位置0,ESP指向位置2*/ 
010913EE add         esp,8  
010913F1 mov         dword ptr [c],eax  
         return 0; 
/*step16 准 备返回0给main函数。先对EAX自身做异或运算,EAX变成0,此时返回值才是真正的不存在了。然后将三个寄存器的值弹出,恢复到 mainCRTStartup()调用main()前的状态,接着释放位置1到位置0之间的预留空间。EPB和ESP都指向位置0。再接着通过比较EBP 与ESP的值,检查ESP是否正常。不相等则调用ILT+305指向的异常处理函数,使ESP等于EBP。正常的话就直接弹出EBP,结束main()函 数,返回mainCRTStartup()函数中继续剩下的操作。*/ 
010913F4 xor         eax,eax  

010913F6 pop         edi  
010913F7 pop         esi  
010913F8 pop         ebx  
010913F9 add         esp,0D8h  
010913FF cmp         ebp,esp  
01091401 call       @ILT+305(__RTC_CheckEsp) (1091136h)  
01091406 mov         esp,ebp  
01091408 pop         ebp  
01091409 ret  





再试试64位整型的long long,C++的实验代码如下(VC6编译器可能不支持long long): 


[cpp] view plaincopyprint?#include "stdafx.h"    
   
long long minus(long long a , long long b)   
{   
         int j=2;   
         return a-b;   
}   
int _tmain(int argc, _TCHAR* argv[])   
{   
         int i=1;   
         long long c=minus(3,1);   
         return 0;   
}   

#include "stdafx.h" 

long long minus(long long a , long long b) 

         int j=2; 
         return a-b; 

int _tmain(int argc, _TCHAR* argv[]) 

         int i=1; 
         long long c=minus(3,1); 
         return 0; 



反汇编代码如下: 


[cpp] view plaincopyprint?long long minus(long long a ,long long b)   
{   
01351380 push        ebp    
01351381 mov         ebp,esp    
01351383 sub         esp,0CCh    
01351389 push        ebx    
0135138A push        esi    
0135138B push        edi    
0135138C lea         edi,[ebp-0CCh]    
01351392 mov         ecx,33h    
01351397 mov         eax,0CCCCCCCCh    
0135139C rep stos    dword ptres:[edi]    
         int j=2;   
0135139E mov         dword ptr [j],2    
         return a-b;   
<SPAN style="COLOR: #ff0000">013513A5 mov         eax,dword ptr [a]    
013513A8 sub         eax,dword ptr [b]    
013513AB mov         edx,dword ptr[ebp+0Ch]    
013513AE sbb         edx,dword ptr[ebp+14h] </SPAN>   
}   
013513B1 pop         edi    
013513B2 pop         esi    
013513B3 pop         ebx    
013513B4 mov         esp,ebp    
013513B6 pop         ebp    
013513B7 ret    
    
int _tmain(int argc, _TCHAR* argv[])   
{   
013513C0 push        ebp    
013513C1 mov         ebp,esp    
013513C3 sub         esp,0DCh    
013513C9 push        ebx    
013513CA push        esi    
013513CB push        edi    
013513CC lea         edi,[ebp-0DCh]    
013513D2 mov         ecx,37h    
013513D7 mov         eax,0CCCCCCCCh    
013513DC rep stos    dword ptres:[edi]    
         int i=1;   
013513DE mov         dword ptr [i],1    
         long long c=minus(3,1);   
013513E5 push        0    
013513E7 push        1    
013513E9 push        0    
013513EB push        3    
013513ED call        minus (13511C2h)    
013513F2 add         esp,10h    
<SPAN style="COLOR: #ff0000">013513F5 mov         dword ptr [c],eax    
013513F8 mov        dword ptr [ebp-14h],edx </SPAN>   
         return 0;   
013513FB xor         eax,eax    
}   
013513FD pop         edi    
013513FE pop         esi    
013513FF pop         ebx    
01351400 add         esp,0DCh    
01351406 cmp         ebp,esp    
01351408 call       @ILT+305(__RTC_CheckEsp) (1351136h)    
0135140D mov         esp,ebp    
0135140F pop         ebp    
01351410 ret     

long long minus(long long a ,long long b) 

01351380 push        ebp  
01351381 mov         ebp,esp  
01351383 sub         esp,0CCh  
01351389 push        ebx  
0135138A push        esi  
0135138B push        edi  
0135138C lea         edi,[ebp-0CCh]  
01351392 mov         ecx,33h  
01351397 mov         eax,0CCCCCCCCh  
0135139C rep stos    dword ptres:[edi]  
         int j=2; 
0135139E mov         dword ptr [j],2  
         return a-b; 
013513A5 mov         eax,dword ptr [a]  
013513A8 sub         eax,dword ptr [b]  
013513AB mov         edx,dword ptr[ebp+0Ch]  
013513AE sbb         edx,dword ptr[ebp+14h]  

013513B1 pop         edi  
013513B2 pop         esi  
013513B3 pop         ebx  
013513B4 mov         esp,ebp  
013513B6 pop         ebp  
013513B7 ret  
  
int _tmain(int argc, _TCHAR* argv[]) 

013513C0 push        ebp  
013513C1 mov         ebp,esp  
013513C3 sub         esp,0DCh  
013513C9 push        ebx  
013513CA push        esi  
013513CB push        edi  
013513CC lea         edi,[ebp-0DCh]  
013513D2 mov         ecx,37h  
013513D7 mov         eax,0CCCCCCCCh  
013513DC rep stos    dword ptres:[edi]  
         int i=1; 
013513DE mov         dword ptr [i],1  
         long long c=minus(3,1); 
013513E5 push        0  
013513E7 push        1  
013513E9 push        0  
013513EB push        3  
013513ED call        minus (13511C2h)  
013513F2 add         esp,10h  
013513F5 mov         dword ptr [c],eax  
013513F8 mov        dword ptr [ebp-14h],edx  
         return 0; 
013513FB xor         eax,eax  

013513FD pop         edi  
013513FE pop         esi  
013513FF pop         ebx  
01351400 add         esp,0DCh  
01351406 cmp         ebp,esp  
01351408 call       @ILT+305(__RTC_CheckEsp) (1351136h)  
0135140D mov         esp,ebp  
0135140F pop         ebp  
01351410 ret   


  

可以发现在进行减法运算和传递返回值时确实发生了变化,说明返回值为64位整型时,会使用EAX和EDX来分别传递返回值的低32位和高32位。 

  

困了,返回值为浮点和超过64位明天接着讨论= =

<think>嗯,用户问的是“C++返回值不同是不是重载”。首先,我需要确认用户的问题到底是什么。他们可能在学习函数重载的时候遇到了疑惑,不确定返回值类型的不同是否构成重载。 根据C++的标准,函数重载的条件是函数名相同,但参数列表不同(参数的类型、个数或顺序不同)。而返回值类型的不同并不作为函数重载的依据。也就是说,如果两个函数只有返回值类型不同,而参数列表完全相同,编译器会报错,认为这是重复定义,而不是合法的重载。 不过,用户可能在实际编程中看到一些特殊情况,或者在某些资料中看到矛盾的例子,所以需要详细解释这一点。同时,用户可能有更深层的疑问,比如为什么返回值类型不能作为重载的条件,或者是否存在例外情况。 接下来,我需要考虑用户可能的背景。可能是一个刚开始学习C++的新手,对函数重载的概念还不是很清楚,或者在编写代码时遇到了相关的错误,想确认原因。他们可能误以为返回值不同就可以重载,导致程序编译失败,所以想弄清楚规则。 此外,用户的问题可能涉及到模板或运算符重载的特殊情况,但一般情况下返回值不同不算重载。需要明确区分这些情况,避免混淆。例如,在模板中,即使返回值不同,如果参数列表不同,也可以形成重载。或者在运算符重载时,某些运算符的返回值类型是固定的,但参数列表不同的话仍然可以重载。 需要给出明确的例子来说明,当仅改变返回值类型时编译器会报错,而改变参数列表则可以正确重载。同时,解释背后的原因,比如函数调用的上下文如何确定使用哪个重载函数,而返回值类型在调用时可能无法被编译器确定,因此不能作为重载的依据。 另外,用户可能想知道是否存在任何例外情况,或者有没有办法绕过这个限制。例如,通过模板元编程或者其他高级技巧,但通常不建议这样做,因为会导致代码难以理解和维护。 总结来说,回答需要明确说明返回值不同不能构成重载,解释标准规定,举例说明正确和错误的情况,并简要讨论可能的例外或相关的高级主题。同时,确保语言通俗易懂,避免使用过多术语,帮助用户建立正确的理解。</think>在C++中,**仅通过返回值类型的不同无法构成函数重载**。以下是详细解释: --- ### 一、函数重载的核心规则 C++函数重载的判定标准是: - **函数名相同**。 - **参数列表不同**(参数类型、个数或顺序不同)。 **返回值类型不同不会触发重载**。若两个函数仅返回值类型不同,编译器会报错: ```cpp int func(int x); // 合法 double func(int x); // 错误!参数列表相同,仅返回值不同(无法重载) ``` --- ### 二、为什么返回值类型不能用于重载? 1. **调用歧义**: C++函数调用时,**调用方可能忽略返回值**。例如: ```cpp func(10); // 调用的是int func(int)还是double func(int)? ``` 编译器无法根据调用上下文推断需要哪个版本。 2. **语言设计原则**: 重载决策基于参数类型,而非返回值,这符合C++的类型系统和编译器的解析逻辑。 --- ### 三、例外场景(看似“返回值不同”的特殊情况) #### 1. 模板中的返回值依赖 通过模板或`auto`推导返回值类型时,**参数列表不同**的模板函数可以形成重载: ```cpp template <typename T> T func(T x); // 版本1:返回T类型 template <typename T> double func(T* x); // 版本2:返回double类型(参数列表不同) ``` #### 2. 运算符重载的固定返回值 某些运算符的返回值类型是固定的(如赋值运算符返回左值引用),但**参数类型不同仍可重载**: ```cpp class MyClass { public: MyClass& operator=(int x); // 返回MyClass& MyClass& operator=(double x); // 参数类型不同,合法重载 }; ``` --- ### 四、总结表 | 场景 | 是否构成重载? | 原因 | |---------------------|---------------|----------------------------------------------------------------------| | 返回值不同,参数相同 | ❌ 否 | 编译器无法区分调用版本 | | 参数不同,返回值不同 | ✅ 是 | 参数列表差异是重载的核心条件 | | 模板/运算符重载 | ✅ 是 | 参数不同或类型推导允许重载(返回值差异是结果,而非重载条件) | --- ### 五、实际代码验证 ```cpp #include <iostream> using namespace std; // 合法重载:参数类型不同 int func(int x) { return x; } double func(double x) { return x; } // 非法重载:仅返回值不同 // int func(char c) { return c; } // double func(char c) { return c; } // 编译错误! int main() { cout << func(10) << endl; // 调用int版本 cout << func(3.14) << endl; // 调用double版本 return 0; } ``` --- ### 六、扩展思考 如果需要根据返回值类型动态选择逻辑,可以通过以下方式实现: 1. **模板 + 类型推导**: ```cpp template <typename T> T processValue(T x); ``` 2. **多态或策略模式**: 通过派生类或函数对象封装不同行为。 --- **结论**:C++不允许仅通过返回值类型不同实现重载,必须依赖参数列表的差异。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值