当前位置:文档之家› !函数返回值

!函数返回值

函数返回值int Count(){int i,j;i=100;j=200;return i+j;}测试函数:void Test(){int k=Count();printf("\n k[%d]\n");}C/C++的函数返回值一般是放在寄存器eax里的,而不是在栈里。

你的这一句int k = Count()的汇编语句就是这样:mov [esp - 4], eax //eax里是300,esp - 4是局部变量k的位置你可以在vc里做个实验:int add(int a, int b){__asm {mov eax,a // 把参数1存入eaxadd eax,b // eax += 参数2, 结果在eax里}}int main(){printf("%d\n", add(3, 4));return 0;}楼主需要了解下寄存器这一概念,我就不把C/C++函数的汇编代码给发出来了。

还有在汇编层面来看,函数的返回值根本就没有定论,函数可以通过多种方式返回。

保存返回值在eax里只是C/C++的一个约定而已。

返回值可以放在栈里,但你在C的语言层面上可能做不到,其实随着函数的结束,mov esp, ebp这条指令过后,函数内部的局部变量就报废了。

如果你之后没改变过栈的内容,你可以用栈来存返回值,但比起用寄存器来存储,存储和读取要慢的多。

自己突发奇想在vc下试了下用栈“返回”值,写了段代码:#include <stdio.h>void __declspec(naked) __stdcall return_a_value(){int local;local = 1990; // 栈空间__asm ret}int main(){int local = 1;return_a_value(); // 用栈返回值printf("%d\n", local);return 0;}汇编看c之一,简单函数调用简单的函数调用,通过简单的函数调用反汇编可以清楚了解如下1.栈到底是什么,如何操纵栈的?2.参数和临时变量是以什么形式在哪存放?3.如何传递返回值?举例:#include <stdio.h>int add(int a,int b){int c=0;c=a+b;return c;}int main(void){int x=0;int y=3;int z=4;x=add(y,z);return 0;}这是一个简单的通过调用函数计算两数之和的程序VC6.0生成的汇编代码如下:add函数{0040D750 push ebp//把main函数的ebp压栈,ebp=1000,esp=8960040D751 mov ebp,esp//得到“新”栈基址,这里的新的意思是每个函数访问属于自己的一块栈区域,其实是相邻的内存区域,或者说栈只有一个。

ebp=896,esp=8960040D753 sub esp,44h//ebp=896,esp=8280040D756 push ebx0040D757 push esi0040D758 push edi//ebp=896,esp=8160040D759 lea edi,[ebp-44h]0040D75C mov ecx,11h0040D761 mov eax,0CCCCCCCCh0040D766 rep stos dword ptr [edi]//初始化内部变量区5: int c=0;0040D768 mov dword ptr [ebp-4],0//c放入“新”栈基址6: c=a+b;0040D76F mov eax,dword ptr [ebp+8]0040D772 add eax,dword ptr [ebp+0Ch]//因为“新”栈基地址就是“旧”栈顶地址,所以通过ebp访问传过来的参数,ebp+8到ebp+c是因为ebp上方的8字节用于存储调用函数的调用地址和“旧”堆栈基地址了。

0040D775 mov dword ptr [ebp-4],eax//运算结果放入c中7: return c;0040D778 mov eax,dword ptr [ebp-4]//用寄存器eax返回结果8: }0040D77B pop edi0040D77C pop esi0040D77D pop ebx//恢复寄存器的值,ebp=896,esp=8280040D77E mov esp,ebp//恢复“旧”栈顶地址,ebp=896,esp=896,此函数堆栈被释放!0040D780 pop ebp//恢复“旧”栈基地址,ebp=1000,esp=900,此时恢复到调用前的栈基地址和顶地址0040D781 ret//返回调用点,ebp=1000,esp=904,调用点地址被弹出,返回到调用点main函数{0040D790 push ebp0040D791 mov ebp,esp//用栈顶地址作为栈基地址,目的是不和调用前栈空间冲突,为了叙述方便假设此时的栈基址ebp=1000,esp=1000。

0040D793 sub esp,4Ch//esp下移,开辟出0x4C字节的空间,这个空间是留给内部参数用的,这个空间的大小随内部变量多少由编译器决定。

ebp=1000,esp=1000-0x4C=9240040D796 push ebx0040D797 push esi0040D798 push edi//保护ebx,esi,edi的值,ebp=1000,esp=924-12=9120040D799 lea edi,[ebp-4Ch]0040D79C mov ecx,13h0040D7A1 mov eax,0CCCCCCCCh0040D7A6 rep stos dword ptr [edi]//把内部参数占用的空间每个字节都初始化为0xCC,这个是为了在DUBUG程序的方便,编译器加入的,如果不在DEBUG状态下,这个区域是没有被初始化的,也就是说是随机值。

12: int x=0;0040D7A8 mov dword ptr [ebp-4],013: int y=3;0040D7AF mov dword ptr [ebp-8],314: int z=4;0040D7B6 mov dword ptr [ebp-0Ch],4//内部变量放入刚才被初始化为0xCC的内部变量区,x占用四字节在地址9996-9999,y,z一次类推15: x=add(y,z);0040D7BD mov eax,dword ptr [ebp-0Ch]0040D7C0 push eax0040D7C1 mov ecx,dword ptr [ebp-8]0040D7C4 push ecx//把参数按照stdcall方式从右到左压栈,ebp=1000,esp=912-8=904,z首先入栈在908-911,y在904-9070040D7C5 call @ILT+15(_add) (00401014)//把返回下一行代码即add esp,8 的地址压栈,转向add函数,ebp=1000,esp= 900,看add函数0040D7CA add esp,8//ebp=1000,esp=912,恢复到压入参数前栈基地址和顶地址,这个步骤叫做堆栈修正0040D7CD mov dword ptr [ebp-4],eax//返回的变量放到x中16: return 0;17: }现在来总结开始提出的三个问题1.栈到底是什么,如何操纵栈的?栈是操作系统分配给程序运行的一块内存区域,有以下特点1.1、改变堆栈用push,pop,用的esp栈顶指针,而读指针则用ebp栈基指针灵活访问1.2、每当一个函数跳转到另一个函数时,会在上一个函数用到的栈空间下方开辟空间2.参数和临时变量是以什么形式在哪存放?2.1、参数放在旧栈的返回地址和旧栈基地址的上方,而临时变量则在新栈的最上方处,变量名会被编译器连接一个地址,程序在被编译成汇编以后,变量名就是虚无了。

3.如何传递返回值?3.1、传递一个值的情况下,通过eax传递可以看出,栈溢出是由于编译器没有检查栈是否还有空间。

函数的返回值,16位/32位收藏同事的一段代码里出现的异常,内存访问错误。

遂一起排查。

使用的是VC6.0,系统是Win2K。

主调函数func1,传进一个数组的首地址。

用数组的下标方式访问,下标是一个子函数func2的返回值。

异常出现在这里。

一开始怀疑传进来的地址有问题,跟踪下来发现是对的。

我怀疑func2的返回值,但看到这个函数很简单,就一句返回一个结构的WORD成员,好象不应该有错。

func2有一个参数,是一个结构指针lp,函数体是return lp->val。

函数声明返回WORD类型,结构的成员val也是WORD类型。

在出事地前加了个printf("%x\n",func2(p)),打印func2的返回值。

结果说明了出错原因,打印值是1520000。

很明显,func2返回了一个DWORD值。

访问数组越界,所以异常。

为什么会这样呢?为什么函数返回类型是WORD,使用时却得到一个DWORD值呢?做了一个尝试,如果把返回值赋给一个WORD类型的局部变量,得到的值是对的;如果加上类型转换(WORD)func2(p)也是对的。

分别看一下这三种情况(一错二对)的汇编码,可以发现,正确的情况,在func1中使用func2的返回值前有一句:0040B7F2 and eax,0FFFFh //清高16位。

因为在调用func2时,结构指针参数的传递用了32位的eax。

func2的汇编码中可以看到,需返回的WORD值放入了16位的ax。

所以在func1使用前清高16位,使用的仍然是eax,但值是所需的正确值。

而错误的情况,即不加转换的直接使用,没有这么一句清高位。

赋值的时候也没有使用movzx 零扩展指令,直接用mov到eax。

0040B7E6 mov edx,dword ptr [ebp-4]0040B7E9 push edx0040B7EA call @ILT+5(_func) (0040100a)0040B7EF add esp,40040B7F2 and eax,0FFFFh //出错的代码没这句。

0040B7F7 push eax0040B7F8 push offset string "a = %d\n" (00420f74)0040B7FD call printf (00401090)0040B802 add esp,8怀疑是编译器的bug。

于是做一个实验。

相关主题