分类目录

展开|收起

看你喜欢

(1) (1) (42) (1) (1) (1) (16) (2) (1) (1) (4) (1) (2) (7) (4) (1) (1) (1) (1) (3) (1) (5) (1) (1) (1) (1) (1) (2) (1) (4) (4) (3) (1) (1) (2) (1) (37) (2) (1) (5) (3) (1) (4) (1) (1) (11) (3) (1) (9) (3) (1) (23) (2) (1) (2) (1) (1) (1) (1)

最新精华

函数调用汇编分析【3】- 实例分析5(结构作为参数和返回值,返回值长度大于8)

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](相当于一个局部临时变量),这个地点的地址不是随便定的。

structure-para-2

说明:为何add()函数返回后不是直接从save中取出赋给c,而非要弄个临时变量呢? 似乎可以不要,但VC编译出的结果就是这样。例如直接写成add(a,b);语句,最后也汇编出把save中保存的返回值保存到temp的代码。似乎是判断出函数有返回值,就先弄个临时变量temp再说, c=add(a,b);实际是c=temp;。这样汇编肯定是有道理的,原因不明。猜测:可能是为了有多个函数调用的时候安排堆栈方便一点,因为可以事先就确定好各个函数返回值的保存地址(临时变量),而save的地点可统一安排,但从debug多个函数调用的汇编代码看又不是这样,release的没继续研究,可能另有其它考虑。哪位知道原因可以告诉我一下。

(3)小结

可以看出由于返回值超过8字节了,无法用edx:eax返回了,这时通过堆栈返回,而返回值在栈中的保存地址是通过是作为函数调用参数传进来的。

如果你的代码中有像这种用结构作为参数和返回值的用法,建议改用指针,因为一来可以减少栈空间的占用,同时也减少了汇编时参数入栈以及赋值,可以提高代码效率。

  打分:5.0/5 (共3人投票)
(浏览总计: 53 次)
Add Comment Register



发表回复

  

  

  

您可以使用这些HTML标签

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>