您的位置:首页 > 其它

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

2006-05-15 08:40 387 查看
为检验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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: