第三章 返回值
众所周知,函数的返回值是由eax传递出来的(浮点数则用浮点数寄存器ST0及浮点指令fld等)。
返回简单值类型的时候,eax中存储的是变量的值。
若以指针、引用做返回值,则会mov指针或引用所在内存中的地址值到eax,然后在外部接收返回值时候,在做一次mov操作。
在32位系统中,寄存器eax与地址值一样,均为32位,因此可用eax直接返回。
测试代码:
int g_int = 100;
int& g_intRef = g_int;
int FuncReturnInt() //返回 值类型
{
004113C0 push ebp
004113C1 mov ebp,esp
004113C3 sub esp,0C0h
004113C9 push ebx
004113CA push esi
004113CB push edi
004113CC lea edi,[ebp-0C0h]
004113D2 mov ecx,30h
004113D7 mov eax,0CCCCCCCCh
004113DC rep stos dword ptr es:[edi] //之上为函数保存环境和初始化代码
return g_int;
004113DE mov eax,dword ptr [g_int (417000h)] //此处直接将g_int的值传给eax
}
004113E3 pop edi //之下为函数恢复环境
004113E4 pop esi
004113E5 pop ebx
004113E6 mov esp,ebp
004113E8 pop ebp
004113E9 ret
int* FuncReturnIntPointer() //返回 指针类型
{
00411400 push ebp
00411401 mov ebp,esp
00411403 sub esp,0C0h
00411409 push ebx
0041140A push esi
0041140B push edi
0041140C lea edi,[ebp-0C0h]
00411412 mov ecx,30h
00411417 mov eax,0CCCCCCCCh
0041141C rep stos dword ptr es:[edi] //之上为函数保存环境和初始化代码
return &g_int;
0041141E mov eax,offset g_int (417000h) //将g_int的地址(offset)传给eax
}
00411423 pop edi //之下为函数恢复环境
00411424 pop esi
00411425 pop ebx
00411426 mov esp,ebp
00411428 pop ebp
00411429 ret
int& FuncReturnIntRef1() //将值类型做引用类型 返回
{
00411440 push ebp
00411441 mov ebp,esp
00411443 sub esp,0C0h
00411449 push ebx
0041144A push esi
0041144B push edi
0041144C lea edi,[ebp-0C0h]
00411452 mov ecx,30h
00411457 mov eax,0CCCCCCCCh
0041145C rep stos dword ptr es:[edi] //之上为函数保存环境和初始化代码
return g_int;
0041145E mov eax,offset g_int (417000h) //将g_int的地址(offset)传给eax
} //与返回指针类型一致
00411463 pop edi //之下为函数恢复环境
00411464 pop esi
00411465 pop ebx
00411466 mov esp,ebp
00411468 pop ebp
00411469 ret
int& FuncReturnIntRef2() //返回 引用类型
{
00411480 push ebp
00411481 mov ebp,esp
00411483 sub esp,0C0h
00411489 push ebx
0041148A push esi
0041148B push edi
0041148C lea edi,[ebp-0C0h]
00411492 mov ecx,30h
00411497 mov eax,0CCCCCCCCh
0041149C rep stos dword ptr es:[edi] //之上为函数保存环境和初始化代码
return g_intRef;
0041149E mov eax,dword ptr [g_intRef (417004h)] //取g_intRef的内容(0x417004)
} //赋值给eax,其内存储的是g_int的地址
004114A3 pop edi //之下为函数恢复环境
004114A4 pop esi
004114A5 pop ebx
004114A6 mov esp,ebp
004114A8 pop ebp
004114A9 ret
PS: 引用做返回值,可以做左值。
PS: 返回值为引用时,与返回值为指针时相同,要注意所引用变量的作用域。
返回局部对象的指针和引用均是不安全的行为。
我们还需要注意的是返回值的接收:返回值类型的函数,其eax存储的是某个值;返回指针类型的函数,其eax存储的是某个指针;返回引用类型的函数,其eax中存储的是其表示的变量的地址。
这里我们需要注意的一种情况
int add(int a,int b)
{
return a+b;
}
int& nRef = add(1,2); //此处包含了引用的初始化与接收返回值两种情况。
//这种情况是不被编译器认可的
我在网上看到有一篇分析引用做返回值的博客中,说明此种情况会被编译器警告。
经我测试,发现VS系列的编译器会直接报error C2440,也许是那篇文章的作者是用的其他编译器(如borland系列编译器)也说不定。
至于VS报错的原因,我们可以猜测一番:
虽然C2440的错误描述是说无法从type1(此例中为int)转到type2(此例中为int&),但是显然这种解释无法满足我们(int n;int& nRef= n;这种形式可是被允许的)。
函数的返回值是由eax传递出来的。也就是说add函数返回的值,没有内存地址,只是存储在一个寄存器eax中。
而引用作为一种特殊的指针,其内存空间中需要存储一个地址值,而add返回值没有地址值,所以无法处理。
这就符合了引用的规则2,引用必须与合法的存储单元关联。