3.5 结构作为参数和返回值(返回值长度大于8)
(1)源程序
typedef struct T_test { char x; int y; int z; }T_test; T_test add(T_test c , T_test d) { T_test e; e.x=c.x+d.x; e.y=c.y+d.y; e.z=c.z+d.z; return e; } void main() { T_test a = {1,2,3}; T_test b = {4,5,6}; T_test c; c=add(a,b); while (1) { } }
(2)汇编代码
1: #include <stdio.h> 2: typedef struct T_test 3: { 4: char x; 5: int y; 6: int z; 7: }T_test; 8: 9: T_test add(T_test c , T_test d) 10: { 0040B650 push ebp // 保存main函数的ebp 0040B651 mov ebp,esp // 置本函数的ebp, 一方面作为add函数的栈基址,另一方 面最后也用这个恢复main的堆栈指针 0040B653 sub esp,4Ch // 保留栈空间 0040B656 push ebx 0040B657 push esi 0040B658 push edi // 保护ebx,esi,edi,即入栈, 因为后面会用到这些寄存器 0040B659 lea edi,[ebp-4Ch] 0040B65C mov ecx,13h 0040B661 mov eax,0CCCCCCCCh 0040B666 rep stos dword ptr [edi] // 初始化保留的栈空间内容为int 3 11: T_test e; 12: e.x=c.x+d.x; 0040B668 movsx eax,byte ptr [ebp+0Ch] // 取c.x 0040B66C movsx ecx,byte ptr [ebp+18h] // 取d.x 0040B670 add eax,ecx // c.x+d.x 0040B672 mov byte ptr [ebp-0Ch],al // e.x=c.x+d.x 13: e.y=c.y+d.y; 0040B675 mov edx,dword ptr [ebp+10h] // 参见上面 0040B678 add edx,dword ptr [ebp+1Ch] 0040B67B mov dword ptr [ebp-8],edx 14: e.z=c.z+d.z; 0040B67E mov eax,dword ptr [ebp+14h] // 参见上面 0040B681 add eax,dword ptr [ebp+20h] 0040B684 mov dword ptr [ebp-4],eax 15: 16: return e; // 如何返回e呢? 这个e长度为12字节,不能用edx:eax返回了 这时通过堆栈返回 0040B687 mov ecx,dword ptr [ebp+8] // 取返回值的保存地址, 是作为函数调用参数传进来的 0040B68A mov edx,dword ptr [ebp-0Ch] 0040B68D mov dword ptr [ecx],edx 0040B68F mov eax,dword ptr [ebp-8] 0040B692 mov dword ptr [ecx+4],eax 0040B695 mov edx,dword ptr [ebp-4] 0040B698 mov dword ptr [ecx+8],edx // 以上保存e到返回值的保存地址中 0040B69B mov eax,dword ptr [ebp+8] // 同时把返回值的保存地址通过eax原样返回 17: } 0040B69E pop edi 0040B69F pop esi 0040B6A0 pop ebx // 恢复ebx,esi,edi,即出栈 0040B6A1 mov esp,ebp // 恢复main的堆栈指针 0040B6A3 pop ebp 0040B6A4 ret 18: 19: 20: void main() 21: { 0040B530 push ebp 0040B531 mov ebp,esp 0040B533 sub esp,7Ch 0040B536 push ebx 0040B537 push esi 0040B538 push edi 0040B539 lea edi,[ebp-7Ch] 0040B53C mov ecx,1Fh 0040B541 mov eax,0CCCCCCCCh 0040B546 rep stos dword ptr [edi] 22: T_test a = {1,2,3}; 0040B548 mov byte ptr [ebp-0Ch],1 // 栈中保存a.x,在低地址 0040B54C mov dword ptr [ebp-8],2 // 栈中保存a.y 0040B553 mov dword ptr [ebp-4],3 // 栈中保存a.z 23: T_test b = {4,5,6}; 0040B55A mov byte ptr [ebp-18h],4 // 栈中保存b.x 0040B55E mov dword ptr [ebp-14h],5 // 栈中保存b.y 0040B565 mov dword ptr [ebp-10h],6 // 栈中保存b.z 24: T_test c; 25: 26: c=add(a,b); 0040B56C sub esp,0Ch // 调整栈指针,以便保存b的副本 0040B56F mov eax,esp 0040B571 mov ecx,dword ptr [ebp-18h] 0040B574 mov dword ptr [eax],ecx 0040B576 mov edx,dword ptr [ebp-14h] 0040B579 mov dword ptr [eax+4],edx 0040B57C mov ecx,dword ptr [ebp-10h] 0040B57F mov dword ptr [eax+8],ecx 0040B582 sub esp,0Ch // 调整栈指针,以便保存a的副本 0040B585 mov edx,esp 0040B587 mov eax,dword ptr [ebp-0Ch] 0040B58A mov dword ptr [edx],eax 0040B58C mov ecx,dword ptr [ebp-8] 0040B58F mov dword ptr [edx+4],ecx 0040B592 mov eax,dword ptr [ebp-4] 0040B595 mov dword ptr [edx+8],eax 0040B598 lea ecx,[ebp-3Ch] // 设置函数返回值的存放地址 0040B59B push ecx 0040B59C call @ILT+30(add) (00401023) // 调用add函数 0040B5A1 add esp,1Ch // 调整堆栈,"a'+b'+save的地址"占用的空间 0040B5A4 mov edx,dword ptr [eax] // 函数返回值的存放地址被返还到eax 0040B5A6 mov dword ptr [ebp-30h],edx // 先把save中保存的返回值保存到temp处 0040B5A9 mov ecx,dword ptr [eax+4] 0040B5AC mov dword ptr [ebp-2Ch],ecx 0040B5AF mov edx,dword ptr [eax+8] 0040B5B2 mov dword ptr [ebp-28h],edx 0040B5B5 mov eax,dword ptr [ebp-30h] // 再把temp赋给c 0040B5B8 mov dword ptr [ebp-24h],eax 0040B5BB mov ecx,dword ptr [ebp-2Ch] 0040B5BE mov dword ptr [ebp-20h],ecx 0040B5C1 mov edx,dword ptr [ebp-28h] 0040B5C4 mov dword ptr [ebp-1Ch],edx 27: 28: while (1) 0040B5C7 mov eax,1 0040B5CC test eax,eax 0040B5CE je main+0A2h (0040b5d2) 29: { 30: } 0040B5D0 jmp main+97h (0040b5c7) 31: 32: } 0040B5D2 pop edi 0040B5D3 pop esi 0040B5D4 pop ebx 0040B5D5 add esp,7Ch 0040B5D8 cmp ebp,esp 0040B5DA call __chkesp (004010e0) 0040B5DF mov esp,ebp 0040B5E1 pop ebp 0040B5E2 ret
说明:add()函数返回后,把edx:eax保存的返回值保存到堆栈的一个临时存放点[ebp-20h](相当于一个局部临时变量),这个地点的地址不是随便定的。
说明:为何add()函数返回后不是直接从save中取出赋给c,而非要弄个临时变量呢? 似乎可以不要,但VC编译出的结果就是这样。例如直接写成add(a,b);语句,最后也汇编出把save中保存的返回值保存到temp的代码。似乎是判断出函数有返回值,就先弄个临时变量temp再说, c=add(a,b);实际是c=temp;。这样汇编肯定是有道理的,原因不明。猜测:可能是为了有多个函数调用的时候安排堆栈方便一点,因为可以事先就确定好各个函数返回值的保存地址(临时变量),而save的地点可统一安排,但从debug多个函数调用的汇编代码看又不是这样,release的没继续研究,可能另有其它考虑。哪位知道原因可以告诉我一下。
(3)小结
可以看出由于返回值超过8字节了,无法用edx:eax返回了,这时通过堆栈返回,而返回值在栈中的保存地址是通过是作为函数调用参数传进来的。
如果你的代码中有像这种用结构作为参数和返回值的用法,建议改用指针,因为一来可以减少栈空间的占用,同时也减少了汇编时参数入栈以及赋值,可以提高代码效率。
最新评论