分享

C 语言函数返回结构体汇编分析

 quasiceo 2014-01-01

最近看Computer Systems: A Programmer's Perspective,大致了解了c语言函数调用返回的堆栈变化. 比如函数返回int,实际上是把返回值存在一个特定的寄存器上(eax?),而返回一个float,是把返回值放在第一个浮点寄存器上%st(0) ,如果是返回结构体,那结构体放在哪里?

分类: C/C++ 汇编 2006-05-15 08:40 1905人阅读 评论(0) 收藏 举报

      为检验VC默认设置下结构的对齐情况,特定义结构如下:
     1: typedef struct _CTest
     2: {
     3:  char aCharacter;
     4:  int iNumber1;
     5:  char bCharacter;
     6:  char cCharacter;
     7:  int iNumber2;
     8: }CTest,*PCTest;
     9:
       GetData()函数返回上面定义的结构,由此可观察 c 语言中返回结构时的细节
    10: CTest GetData()
    11: {
00401000 >/$ 55             PUSH EBP
00401001  |. 8BEC           MOV EBP,ESP
00401003  |. 83EC 10        SUB ESP,10            -->为 tem 分配空间,共16个字节, 即下图栈中 28H--34H 的空间

此时栈的情况:
      +-------------------------+
 (64) | 调用 main 函数前的EBP
      +--------------------------
 (60) +
      +--------------------------
 (5C) +
      +--------------------------
 (58) +
      +--------------------------
 (54) +
      +--------------------------
 (50) +
      +--------------------------
 (4C) +
      +--------------------------
 (48) +
      +--------------------------
 (44) +
      +--------------------------
 (40) + GetData() 返回时所用临时变量的首地址
      +--------------------------
 (44) + GetData() 返回地址
      +--------------------------
EBP-->| 调用 GetData 函数前的EBP
      +--------------------------
 (34) +
      +--------------------------
 (30) +
      +--------------------------
 (2C) +
      +--------------------------
ESP-->+
      +--------------------------

 

    12:  CTest tem;
    13:  printf( "run in GetData/n");

00401006  |. 68 40804000    PUSH test.00408040                       ; /format = "run in GetData"
0040100B  |. E8 93000000    CALL test.printf                         ; /printf
00401010  |. 83C4 04        ADD ESP,4

    14:  tem.aCharacter = 'a';

00401013  |. C645 F0 61     MOV BYTE PTR SS:[EBP-10],61

    15:  tem.bCharacter = 'b';

00401017  |. C645 F8 62     MOV BYTE PTR SS:[EBP-8],62

    16:  tem.cCharacter = 'c';

0040101B  |. C645 F9 63     MOV BYTE PTR SS:[EBP-7],63

    17:  tem.iNumber1 = 1;

0040101F  |. C745 F4 010000>MOV DWORD PTR SS:[EBP-C],1

    18:  tem.iNumber2 = 2;

00401026  |. C745 FC 020000>MOV DWORD PTR SS:[EBP-4],2

此时栈的情况:
      +-------------------------+
 (64) | 调用 main 函数前的EBP
      +--------------------------
 (60) +
      +--------------------------
 (5C) +
      +--------------------------
 (58) +
      +--------------------------
 (54) +
      +--------------------------
 (50) +
      +--------------------------
 (4C) +
      +--------------------------
 (48) +
      +--------------------------
 (44) +
      +--------------------------
 (40) + GetData() 返回时所用临时变量的首地址
      +--------------------------
 (44) + GetData() 返回地址
      +--------------------------
EBP-->| 调用 GetData 函数前的EBP
      +--------------------------
 (34) +      00 00 00 02
      +--------------------------
 (30) +      xx xx 63 62
      +--------------------------
 (2C) +      00 00 00 01
      +--------------------------
ESP-->+      xx xx xx 61
      +--------------------------

    19:  return tem;

0040102D  |. 8B45 08        MOV EAX,DWORD PTR SS:[EBP+8]      --> 取得临时变量的首地址并存于 EAX 中  (该地址作为GetData()的参数传进来)

  下面代码将 tem 复制给 值传递时所用的临时变量:
00401030  |. 8B4D F0        MOV ECX,DWORD PTR SS:[EBP-10]
00401033  |. 8908           MOV DWORD PTR DS:[EAX],ECX
00401035  |. 8B55 F4        MOV EDX,DWORD PTR SS:[EBP-C]
00401038  |. 8950 04        MOV DWORD PTR DS:[EAX+4],EDX
0040103B  |. 8B4D F8        MOV ECX,DWORD PTR SS:[EBP-8]
0040103E  |. 8948 08        MOV DWORD PTR DS:[EAX+8],ECX
00401041  |. 8B55 FC        MOV EDX,DWORD PTR SS:[EBP-4]
00401044  |. 8950 0C        MOV DWORD PTR DS:[EAX+C],EDX
00401047  |. 8B45 08        MOV EAX,DWORD PTR SS:[EBP+8]

此时栈的情况:
      +-------------------------+
 (64) | 调用 main 函数前的EBP
      +--------------------------
 (60) +
      +--------------------------
 (5C) +
      +--------------------------
 (58) +
      +--------------------------
 (54) +
      +--------------------------
 (50) +      00 00 00 02
      +--------------------------
 (4C) +      xx xx 63 62
      +--------------------------
 (48) +      00 00 00 01
      +--------------------------
 (44) +      xx xx xx 61
      +--------------------------
 (40) + GetData() 返回时所用临时变量的首地址
      +--------------------------
 (44) + GetData() 返回地址
      +--------------------------
EBP-->| 调用 GetData 函数前的EBP
      +--------------------------
 (34) +      00 00 00 02
      +--------------------------
 (30) +      xx xx 63 62
      +--------------------------
 (2C) +      00 00 00 01
      +--------------------------
ESP-->+      xx xx xx 61
      +--------------------------


    20: }

0040104A  |. 8BE5           MOV ESP,EBP
0040104C  |. 5D             POP EBP
0040104D  /. C3             RETN

 

0040104E     CC             INT3
0040104F     CC             INT3


    21:
    22: int main()
    23: {
00401050 >/$ 55             PUSH EBP
00401051  |. 8BEC           MOV EBP,ESP
00401053  |. 83EC 20        SUB ESP,20           --> 32 字节的临时变量,其中 tem 占用16字节,调用 GetData() 时由于函数返回 CTest 类型
                                                     而 C 语言中 return 语句返回的变量是值传递,所以该变量需占用 16 字节,总共 32 字节。

此时栈的情况:
      +-------------------------+
EBP-->| 调用 main 函数前的EBP
      +--------------------------
 (60) +
      +--------------------------
 (5C) +
      +--------------------------
 (58) +
      +--------------------------
 (54) +
      +--------------------------
 (50) +
      +--------------------------
 (4C) +
      +--------------------------
 (48) +
      +--------------------------
ESP-->+
      +--------------------------
 (40) +
      +--------------------------
 (3C) +
      +--------------------------

    24:  CTest tem = GetData();

00401056  |. 8D45 E0        LEA EAX,DWORD PTR SS:[EBP-20]            --> 获取 GetData() 函数返回 CTest 类型时所用临时变量的有效地址
00401059  |. 50             PUSH EAX                                 ; /Arg1     --> 将该有效地址作为参数传递给 GetData()
0040105A  |. E8 A1FFFFFF    CALL test.GetData                        ; /GetData
0040105F  |. 83C4 04        ADD ESP,4

此时栈的情况:
      +-------------------------+
EBP-->| 调用 main 函数前的EBP
      +--------------------------
 (60) +
      +--------------------------
 (5C) +
      +--------------------------
 (58) +
      +--------------------------
 (54) +
      +--------------------------
 (50) +      00 00 00 02
      +--------------------------
 (4C) +      xx xx 63 62
      +--------------------------
 (48) +      00 00 00 01
      +--------------------------
ESP-->+      xx xx xx 61
      +--------------------------

  下面代码将 GetData() 返回时的临时变量复制给,其中 EAX 存放着该临时变量的首地址:

00401062  |. 8B08           MOV ECX,DWORD PTR DS:[EAX]
00401064  |. 894D F0        MOV DWORD PTR SS:[EBP-10],ECX
00401067  |. 8B50 04        MOV EDX,DWORD PTR DS:[EAX+4]
0040106A  |. 8955 F4        MOV DWORD PTR SS:[EBP-C],EDX
0040106D  |. 8B48 08        MOV ECX,DWORD PTR DS:[EAX+8]
00401070  |. 894D F8        MOV DWORD PTR SS:[EBP-8],ECX
00401073  |. 8B50 0C        MOV EDX,DWORD PTR DS:[EAX+C]
00401076  |. 8955 FC        MOV DWORD PTR SS:[EBP-4],EDX

此时栈的情况:
      +-------------------------+
EBP-->| 调用 main 函数前的EBP
      +--------------------------
 (60) +      00 00 00 02
      +--------------------------
 (5C) +      xx xx 63 62
      +--------------------------
 (58) +      00 00 00 01
      +--------------------------
 (54) +      xx xx xx 61
      +--------------------------
 (50) +      00 00 00 02
      +--------------------------
 (4C) +      xx xx 63 62
      +--------------------------
 (48) +      00 00 00 01
      +--------------------------
ESP-->+      xx xx xx 61
      +--------------------------

此时主函数中的 tem 已经是我们所希望和值了.
(注: 由于此处所返回的结构体较大, 所以临时变量全部保存在内存中, 如果结构体较小, 则函数返回时所用的临时变量可保存在寄存器中.)

    25:  printf("main: %d, %d, %c, %c, %c/n", tem.iNumber1, tem.iNumber2, tem.aCharacter, tem.bCharacter, tem.cCharacter);

00401079  |. 0FBE45 F9      MOVSX EAX,BYTE PTR SS:[EBP-7]
0040107D  |. 50             PUSH EAX                                 ; /<%c>
0040107E  |. 0FBE4D F8      MOVSX ECX,BYTE PTR SS:[EBP-8]            ; |
00401082  |. 51             PUSH ECX                                 ; |<%c>
00401083  |. 0FBE55 F0      MOVSX EDX,BYTE PTR SS:[EBP-10]           ; |
00401087  |. 52             PUSH EDX                                 ; |<%c>
00401088  |. 8B45 FC        MOV EAX,DWORD PTR SS:[EBP-4]             ; |
0040108B  |. 50             PUSH EAX                                 ; |<%d>
0040108C  |. 8B4D F4        MOV ECX,DWORD PTR SS:[EBP-C]             ; |
0040108F  |. 51             PUSH ECX                                 ; |<%d>
00401090  |. 68 50804000    PUSH test.00408050                       ; |format = "main: %d, %d, %c, %c, %c"
00401095  |. E8 09000000    CALL test.printf                         ; /printf
0040109A  |. 83C4 18        ADD ESP,18

    26:  return 0;

0040109D  |. 33C0           XOR EAX,EAX

    27: }

0040109F  |. 8BE5           MOV ESP,EBP
004010A1  |. 5D             POP EBP
004010A2  /. C3             RETN

结论:
    C 语言中函数返回结构体时如果结构体较大, 则在调用函数中产生该结构的临时变量,并将该变量首地址传递给被调用函数,被调用函数返回时根据该地址修改此临时变量的内容,之后在调用函数中再将该变量复制给用户定义的变量,这也正是 C 语言中所谓值传递的工作方式。
    如果结构体较小, 则函数返回时所用的临时变量可保存在寄存器中,返回后将寄存器的值复制给用户定义的变量即可。

测试环境及工具:
    VC++.NET 7.1 Realease 版本;禁止优化
    OllyDbg V1.10


附:测试所用的 C 语言代码:

typedef struct _CTest
{
 char aCharacter;
 int iNumber1;
 char bCharacter;
 char cCharacter;
 int iNumber2;
}CTest,*PCTest;

CTest GetData()
{
 CTest tem;
 printf( "run in GetData/n");
 tem.aCharacter = 'a';
 tem.bCharacter = 'b';
 tem.cCharacter = 'c';
 tem.iNumber1 = 1;
 tem.iNumber2 = 2;
 return tem;
}

int main()
{
 CTest tem = GetData();
 printf("main: %d, %d, %c, %c, %c/n", tem.iNumber1, tem.iNumber2, tem.aCharacter, tem.bCharacter, tem.cCharacter);
 return 0;
}

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多