当前位置:编程学习 > VC++ >>

vc如何返回函数结果及压栈参数

首先说明,本文的分析对象是运行在IA32平台上的程序,试验用的编译器是Visual C++ 6.0中的cl.exe(Microsoft 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86)。
    IA32程序利用程序堆栈来支持过程(procedure)调用,比如创建局部数据、传递参数、保存返回值信息、保存待今后恢复的寄存器等。为了一个过程调用而分配的堆栈空间称为一个stack frame。最顶层的stack frame由两个寄存器标识:ebp保存stack frame的基址,esp保存栈顶地址,因为在过程执行的时候栈顶寄存器的值会经常变化,所以绝大多数的信息都是通过ebp来相对寻址的。图1描绘了stack frame的基本结构。注意在IA32机器上,栈是向低地址方向增长,所以栈顶的地址值小于等于栈底的地址值。
 
                   stack "bottom"
                  ________________ ______
                 |                |  |
                 |       .        |  |
                 |                |  |
                 |       .        |  |
                 |                |  |--->Earlier frames
                 |       .        |  |
                 |                |  |
                 |       .        |  |       
    |            |                |  |
    |            |________________|__|___
    |            |       .        |  |
    |            |       .        |  |
    |            |       .        |  |
Decreasing       |________________|  |
 address         |   Argument n   |  |
    |       4m-->|________________|  |
    |   (m为整数) |       .        |  |-->Caller's frame
    |            |       .        |  |
    |            |________________|  |
    |            |   Argument 1   |  |
    |       +8-->|________________|  |
    V            | Return address |  | 
            +4-->|________________|__|___
                 |   Saved  ebp   |  |
 Frame Pointer-->|________________|  |
           ebp   |Saved  registers|  |
                 |local  variables|  |
                 |      and       |  |-->Current frame
                 |   temporaries  |  |
                 |________________|  |
                 |    Argument    |  |
                 |   build area   |  |
 Stack pointer-->|________________|__|___
           esp       stack "top"
 
                        图1
 
    调用C函数时,不管参数类型如何(包括浮点和struct类型)调用者(caller)负责从右至左将参数依次压栈,最后压入返回地址并跳转到被调函数入口处执行,其中每个参数都要按4字节地址对齐。按照地址来说被传递的参数和返回地址都是位于调用者stack frame中的。如果函数的返回值类型是整型(包括char,short,int,long及它们的无符号型)或指针类型的话,那么就利用EAX寄存器来传回返回值。请看下面的函数:
 
long foo_long(long offset)
{
   long val = 2006 + offset;
   return val;
}
 
用 /c /Od /FAsc 的编译选项(下文均同)编译出如下的汇编码:
 
PUBLIC    _foo_long
_TEXT    SEGMENT
_offset$ = 8
_val$ = -4
_foo_long PROC NEAR
 
; 38   : {
 
push    ebp ;保存调用者的stack frame基址
mov     ebp, esp
push    ecx
 
; 39   :    long val = 2006 + offset;
 
mov     eax, DWORD PTR _offset$[ebp]
add     eax, 2006
mov     DWORD PTR _val$[ebp], eax
 
; 40   :    return val;
 
; 将返回值保存进eax寄存器
mov  eax, DWORD PTR _val$[ebp]
 
; 41   : }
 
mov     esp, ebp
pop     ebp
ret     0
 
_foo_long ENDP
_TEXT    ENDS
 
    如果函数返回的是结构体数据(非结构体指针)那得通过哪个中转站传递呢?这就要分三种情况:
 
1、结构体大小不超过4字节,那么仍然使用EAX寄存器传递返回值。比如:
 
/* 4字节大小结构体 */
typedef struct small_t
{
   char m1;
   char m2;
   char m3;
   char m4;
} small_t;
 
small_t create_small(void)
{
   small_t small = {'s','m','a','l'};
   return small;
}
 
void call_small(void)
{
   small_t small_obj = create_small();
}
 
编译出的汇编码是:
 
;create_small函数
PUBLIC    _create_small
_TEXT    SEGMENT
_small$ = -4
_create_small PROC NEAR
 
; 16   : {
 
push     ebp
mov     ebp, esp
push     ecx
 
; 17   :    small_t small = {'s','m','a','l'};
 
mov BYTE PTR _small$[ebp], 115 ; 00000073H
mov BYTE PTR _small$[e
补充:软件开发 , Vc ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,