這幾天一直在琢磨C能不能返回結構體的問題,在網上看到一篇貼子,覺得不錯,特此轉貼:
為檢驗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
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;
}