返回值研究

原创 2012年04月05日 09:53:23

之前学习c++的时候,对返回值的问题一直弄得不是很明白,今天自己下决心研究一下返回值的问题,返回值的类型大概可以分为这几类:基本类型,自定义类型,指针,引用(引用的本质其实就是指针)。

1、先看返回为基本类型的

int类型的

1、先看返回基本类型的
1:    #include<iostream.h>
2:    #include<string.h>
3:    int fun()
4:    {
00401080   push        ebp
00401081   mov        ebp,esp
00401083   sub         esp,44h
00401086   push        ebx
00401087   push        esi
00401088   push        edi
00401089   lea         edi,[ebp-44h]
0040108C   mov         ecx,11h
00401091   mov         eax,0CCCCCCCCh
00401096   rep stos    dword ptr [edi]
//前面的所有的代码都是为了保存现场和初始化该函数的栈空间。
5:        int a=0x12345;
00401098   mov         dword ptr [ebp-4],12345h
6:        return a;
0040109F   mov         eax,dword ptr [ebp-4]
7:    }//后面的代码为将寄存器恢复到调用之前状态,即恢复现场。
004010A2   pop         edi
004010A3   pop         esi
004010A4   pop         ebx
004010A5   mov         esp,ebp
004010A7   pop         ebp
004010A8   ret

 

根据反汇编的代码,非浮点数的基本类型的返回值放在了eax寄存器里面

浮点数类型的

8:    double *dfun()
9:    {
//略去初始化代码后面不再陈述
10:       double a=1.5;
004010D8   mov         dword ptr [ebp-8],0
004010DF   mov         dword ptr [ebp-4],3FF80000h
11:       return a;
004010E6   fld         qword ptr [ebp-8]
13:   }

 

double类型的返回值放在浮点寄存器里面,fld  IN指令表示将数IN压入ST(0)中,ST(0)-ST(7)8个栈空间来实现浮点寄存器。有关浮点数栈空间的介绍可以参考其他资料。

2、返回值为指针类型的

(1)指向临时变量

浮点类型指针

9:       double a=1.5;

004010D8  mov         dword ptr [ebp-8],0

004010DF  mov         dword ptr[ebp-4],3FF80000h

10:      return &a;

004010E6  lea         eax,[ebp-8]

 

int类型指针

15:      int * pNum;

16:      int a =8;

00401118  mov         dword ptr [ebp-8],8

17:      pNum=&a;

0040111F  lea         eax,[ebp-8]

00401122  mov         dword ptr [ebp-4],eax

18:      return pNum;

00401125  mov         eax,dword ptr [ebp-4]


由上面可见,浮点类型指针和int类型指针的返回值都是放在寄存器里面,被调用函数的临时变量的值都存放被调用函数的栈空间里面,临时变量的指针指向的地址也就是被调用函数的栈空间地址,当函数调用结束后,被调用函数的栈空间就会被清空,临时变量被销毁,此时,指向临时变量的指针已经没有意义,如果在调用函数中使用该指针来访问里面的数据就会引发错误。

(2)指向常量

22:      char* pchar;

23:      pchar="hello world";

00401158  mov         dword ptr [ebp-4],offsetstring "hello world" (0042c01c)

24:      return pchar;

0040115F  mov         eax,dword ptr [ebp-4]


常量存在于常量存储区,如上面的的的地址就是0042c01c,最后返回的时候,会把常量的地址返回给调用函数,常量在被调用函数结束后是不会被销毁的,在调用函数中用指针来访问该常量能得到正确的结果。静态局部变量虽然没有测试过,但是道理和常量应该是一样的。返回局部静态变量的指针也能得到正确的结果。

(3)指向动态申请的空间

23:       char* pchar=new char[10];
00401148   push        0Ah
0040114A   call        operator new (00401230)
0040114F   add         esp,4
00401152   mov         dword ptr [ebp-8],eax
00401155   mov         eax,dword ptr [ebp-8]
00401158   mov         dword ptr [ebp-4],eax
24:       strcpy(pchar,"go back");
0040115B   push        offset string "go back" (0042c01c)
00401160   mov         ecx,dword ptr [ebp-4]
00401163   push        ecx
00401164   call        strcpy (0040d380)
00401169   add         esp,8
25:       return pchar;
0040116C   mov         eax,dword ptr [ebp-4]
26:   }

 

我们可以查看eax的值,这里为0x3382e68,而此时esp的值为0012ffeb,很明显eax中的地址不在该函数的栈空间中。由于动态申请的空间是在堆内存中,不在被调用函数的栈空间中,所以调用结束后不会被销毁,返回的结果后可以正确使用。但是,必须记住在调用函数里面将动态申请的空间释放掉。不然会引起内存泄露

(4)返回从调用者传递过来的指针

如果没有重新指向被调用函数的局部变量(动态申请、静态、常量除外),则返回结果是正确的。

3、返回引用

4:       int a=1131;

00401098  mov         dword ptr [ebp-4],46Bh

5:       return a;

0040109F  lea         eax,[ebp-4]


 注意到返回的就是变量a的地址,所以返回指针和返回引用道理是相同的。

4、返回对象

#include<iostream.h>
#include<memory.h>
class Set
{//集合类
         private:
                   int*pNum;
                   int count;//元素个数
         public:
                   Set();
                   ~Set();
                   Set(constSet&);
                   voidAdd(int num);//添加元素
                   voidRemove(int num);//移除元素
                   Setoperator+(const Set&);
                   Set&operator=(const Set&);
                   boolcheckSameNum(int Num);//判断元素是否在集合中
                   friendostream& operator<<(ostream & out,const Set&);
};
Set::Set()
{
         pNum=newint[100];
         memset(pNum,0,100*sizeof(int));
         count=0;
         cout<<"Incounstructor\n";
}
Set::~Set()
{
         delete[]pNum;
         cout<<"Indestructor\n";
}
Set::Set(const Set& set)
{
         inti;
         pNum=newint[100];
         count=set.count;
         for(i=0;i<set.count;i++)
         {
                   pNum[i]=set.pNum[i];
         }
         cout<<"Incopy constructor\n";
}
bool Set::checkSameNum(int Num)
{
         inti;
         for(i=0;i<count;i++)
         {
                   if(pNum[i]==Num)return true;
         }
         returnfalse;
}
void Set::Add(int Num)
{
         if(count>=99)return;
         if(!checkSameNum(Num))
         {
                   //count++;
                   pNum[count++]=Num;
         }
}
void Set::Remove(int Num)
{
         inti;
         for(i=0;i<count;i++)
         {
                   if(pNum[i]==Num)
                   {
                            pNum[i]=pNum[--count];
                            return;
                   }
         }
}
Set Set::operator+(const Set& set)
{
         inti;
         Sets(*this);
         for(i=0;i<set.count&&s.count<100;i++)
         {
                   //s.count=i;
                   s.Add(set.pNum[i]);
         }
        
         returns; 
}
Set& Set::operator=(const Set& set)
{
         inti;
         for(i=0;i<set.count;i++)
         {
                   pNum[i]=set.pNum[i];
         }
         count=set.count;
         return*this;
}
ostream& operator<<(ostream&out,const Set& set)
{
         inti;
         for(i=0;i<set.count;i++)
         {
                   out<<set.pNum[i]<<"  ";
                   if((i+1)%5==0)out<<endl;
         }
         returnout;
}
int main()
{
         Sets0,s1,s2;   
         s0.Add(3);
         s0.Add(7);
         s1.Add(9);
         s2=s0+s1;//测试句子     
         cout<<s0<<endl;
         cout<<s1<<endl;
         cout<<s2<<endl;     
         return0;
}



图(1)


刚开始时,s0,s1,s2在main函数栈空间中的地址,对象占8个字节(int和int*分别占4个字节)。

 

图(2)返回局部对象s,调用者为main函数

 

表达式s0+s1返回局部变量s(最终返回的对象并不是s,而是一个临时的对象),s的地址(0x0012fed0)位于operator+的栈空间中,s的pNum指向的地址存在于堆空间中,函数返回的时候,先调用拷贝构造函数,将s拷贝到调用函数的栈空间中。

图(3)在main函数栈空间中拷贝构造函数,调用者为operator+


在main函数栈空间中构造临时对象,地址为(0x0012ff54)。如果自定义对象中存在动态申请的内存,必须自己重载拷贝构造函数,不然系统默认的拷贝构造函数仅仅拷贝地址,而不去开辟内存,就会有两个对象中的变量指向同一片内存区,当一个对象被析构掉后,申请的内存会被释放,再去析构另一个对象时,会发生释放已经释放的内存,出现严重的错误。

图(4)析构函数,调用者为operator+

 

调用析构函数,将局部变量s(0x12fed0)给析构掉,this指针指向的地址为s的地址。所以真正返回的并不是s,s在这里已经被析构掉了,而是临时对象,临时对象的值和s是一样的。

图(5)调用operator=,调用者为main函数

 

将临时对象(0x0012ff54)赋值给s2(0x12ff5c),如自定义对象中存在动态申请的内存,必须重载赋值运算符,原理同上面的拷贝构造函数。

图(6)临时对象(Set*)0x0012ff54


在main函数观察临时对象,将临时对象的地址强制转换成Set*即可观察到。

 

图(7)临时对象(0x0012ff54)调用析构函数

当赋值结束后,会将临时对象销毁,调用临时对象的析构函数。


总结:

(1)返回非指针类型,在调用函数中能得到正确的结果。字符型和整形变量返回值存放在寄存器eax中,浮点数返回值存放在浮点计数器中,自定义对象返回值存放在调用函数的栈空间中。

(2)返回指向非静态局部变量的指针或者引用,在调用函数中得不到正确的结果。

(3)返回指向常量、动态申请的内存、静态局部变量的指针时,在调用函数中能得到正确的结果,返回动态申请的内存是,一定要记得在调用函数中将动态申请的空间释放掉。

SSL_shutdown返回值的研究(1)

Format LIBS := CSSL #include int SSL_shutdown(SSL *ssl) ssl A pointer to a token returned on...
  • androidzhaoxiaogang
  • androidzhaoxiaogang
  • 2013年01月01日 00:41
  • 5971

内部类,形式参数和返回值问题研究,API的使用

一.内部类 在一个类中定义另一个类,那么把这种情况叫:内部类:        举例:                 在类A中定义一个类B,那么类B就是类A的内部类,同理,类A就是类B的外部类 ...
  • YQL0258
  • YQL0258
  • 2017年10月26日 16:45
  • 162

计算机网络研究方向和网络安全问题

1)密码理论:研究高强度的密码理论,高速加解密算法,密码分析与攻击算法等     2)安全协议:研究安全协议的形式化方法,如BAN逻辑,串空间,SPI演算等;研究安全多方计算的基础理论  ...
  • moqingxinai2008
  • moqingxinai2008
  • 2016年12月28日 15:33
  • 601

前瞻性队列、回顾性队列、病例对照研究

一、前瞻性队列研究 研究对象的确定与分组根据研究开始时的实际情况,研究的结局需随访观察一段时间才能得到,这种研究可信度高、偏倚少,但费时、费人力、物力、财力。 二、回顾性队列研究 回顾性队列研究...
  • u012344939
  • u012344939
  • 2017年02月24日 18:31
  • 1058

研究linux内核的目的

一.研究内核的目的  欲举其事,先正其道。要谈论内核的研读以及交流心得,前提必须得有一个恰当准确的目的,方能收获良多。 很多人都有这样一个疑惑,为什么要研究内核呢?我们又不是内核开发者(或...
  • SunboyJohn690905084
  • SunboyJohn690905084
  • 2013年11月08日 18:14
  • 1421

做课题与科研项目常用的研究方法

研究方法不出“硬伤” 1、常用的研究方法有文献研究法、教育观察法、经验总结法、个案研究法、行动研究法、实验研究法、历史研究法、调查研究法、比较研究法、叙事研究法等。 2、研究类型或途径、或...
  • neilol
  • neilol
  • 2015年06月18日 15:28
  • 3082

CSS设计彻底研究

选择器CSS选择器主要包括id、class和标记选择器。复合选择器就是两个或多个基本选择器,通过不同方式连接而成的选择器。 交集选择器由连个选择器直接连接构成,其中第一个必须是标记选择器,第二个必须...
  • sysuzhyupeng
  • sysuzhyupeng
  • 2017年05月02日 14:14
  • 735

数据结构的定义和研究的内容

定义: 按照某种逻辑关系组织起来的一批数据,用一定的存储方式存储在计算机的存储器中,并在这些数据上定义一个运算的集合,就成为一个数据结构。 数据结构研究的内容如下: (1)数据的逻辑结构:按照某...
  • Johnnyzhunan
  • Johnnyzhunan
  • 2016年08月28日 18:21
  • 2431

图像显著度(saliency detection)研究现状调研

已下博文转自 http://blog.csdn.net/huangbo10/article/details/19788547?utm_source=tuicool 关于显著度的研究是从生物研究发...
  • wangweiboss
  • wangweiboss
  • 2015年04月19日 16:06
  • 981

计算机科学研究方向介绍

[ taisha 2007 ] [一]计算机研究方向包括: (1)计算理论: (Theory of computation) 计算理论是关于计算和计算机械的数学理论。 主要内容包括: 1...
  • mozart_cai
  • mozart_cai
  • 2013年12月30日 12:50
  • 1618
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:返回值研究
举报原因:
原因补充:

(最多只允许输入30个字)