现在位置: 首页 > 笔记 > 心得 > 正文

函数调用汇编分析【3】- 实例分析2(传入指针)

2013年12月01日 暂无评论 ⁄ 被围观 (浏览总计: 57 次)+

3.2 传入指针

3.2.1 直接传入指针,指针指向简单类型

(1)源程序

#include <stdio.h>
int add(int* c , int* d)
{
    int e;
    e=*c+*d;  
    return e;
}

void main()
{
    int a = 1;
    int b = 2;
    int c;
    int* p1;
    int* p2;

    p1=&a;
    p2=&b;

    c=add(p1,p2);
    while (1)
    {
    }
}

(2)汇编代码

1:    #include <stdio.h>
2:
3:    int add(int* c , int* d)
4:    {
0040B650   push        ebp
0040B651   mov         ebp,esp
0040B653   sub         esp,44h
0040B656   push        ebx
0040B657   push        esi
0040B658   push        edi
0040B659   lea         edi,[ebp-44h]
0040B65C   mov         ecx,11h
0040B661   mov         eax,0CCCCCCCCh
0040B666   rep stos    dword ptr [edi]
5:        int e;
6:        e=*c+*d;
0040B668   mov         eax,dword ptr [ebp+8]   // 取第一个参数,即a的地址指针
0040B66B   mov         ecx,dword ptr [eax]     // *c,实际获得a的值
0040B66D   mov         edx,dword ptr [ebp+0Ch] // 取第二个参数,即b的地址指针
0040B670   add         ecx,dword ptr [edx]     // *c+*d
0040B672   mov         dword ptr [ebp-4],ecx   // e=*c+*d
7:        return e;
0040B675   mov         eax,dword ptr [ebp-4]   // e通过eax寄存器返回
8:    }
0040B678   pop         edi
0040B679   pop         esi
0040B67A   pop         ebx
0040B67B   mov         esp,ebp
0040B67D   pop         ebp
0040B67E   ret

9:
10:
11:   void main()
12:   {
0040B530   push        ebp
0040B531   mov         ebp,esp
0040B533   sub         esp,54h
0040B536   push        ebx
0040B537   push        esi
0040B538   push        edi
0040B539   lea         edi,[ebp-54h]
0040B53C   mov         ecx,15h
0040B541   mov         eax,0CCCCCCCCh
0040B546   rep stos    dword ptr [edi]
13:       int a = 1;
0040B548   mov         dword ptr [ebp-4],1  // 栈中保存a 
14:       int b = 2;
0040B54F   mov         dword ptr [ebp-8],2  // 栈中保存b  
15:       int c; 
16:       int* p1;
17:       int* p2;
18:
19:       p1=&a;
0040B556   lea         eax,[ebp-4]              // 取a的地址
0040B559   mov         dword ptr [ebp-10h],eax  // 栈中保存p1,且p1=&a
20:       p2=&b;
0040B55C   lea         ecx,[ebp-8]              // 取b的地址
0040B55F   mov         dword ptr [ebp-14h],ecx  // 栈中保存p2
21:
22:       c=add(p1,p2);
0040B562   mov         edx,dword ptr [ebp-14h] 
0040B565   push        edx                 // p2的值入栈作为add的第二个参数
0040B566   mov         eax,dword ptr [ebp-10h]
0040B569   push        eax                  // p1的值入栈作为add的第一个参数
0040B56A   call        @ILT+45(add) (00401032)  // 调用add函数
0040B56F   add         esp,8                    // 调整堆栈指针
0040B572   mov         dword ptr [ebp-0Ch],eax  // add返回值赋给c
23:
24:       while (1)
0040B575   mov         ecx,1
0040B57A   test        ecx,ecx
0040B57C   je          main+50h (0040b580)
25:       {
26:       }
0040B57E   jmp         main+45h (0040b575)
27:   }
0040B580   pop         edi
0040B581   pop         esi
0040B582   pop         ebx
0040B583   add         esp,54h
0040B586   cmp         ebp,esp
0040B588   call        __chkesp (004010e0)
0040B58D   mov         esp,ebp
0040B58F   pop         ebp
0040B590   ret

(3)小结

可以看出入栈的是一个地址,而且这个地址也是一个副本,并不是原来的p1和p2,所以如果在callee中改变了副本的值,实际是不会影响到p1和p2的,但如果改变其值所代表的地址的内容,实际也就是改变了p1和p2所指向地址的内容。经常说指针传递能节省栈空间,因为无需构造变量的副本而只需要传递地址,可在这个例子中看不出来好处,因为要传递值的变量仅仅是一个int,而指针占用的空间和int类型一样大,都是4字节,所以体现不出指针的优势。后面有传递指向结构体的指针的例子,就可以看出指针传递的优势了。

同时可以看出,指针传递实际也是一种值传递,只不过值是一个地址。

3.2.2 用&传入地址,等价传入指针

(1)源程序

#include <stdio.h>
int add(int* c , int* d)
{
    int e;
    e=*c+*d;  
    return e;
}

void main()
{
    int a = 1;
    int b = 2;
    int c;

    c=add(&a,&b);

    while (1)
    {
    }
}

(2)汇编代码

1:    #include <stdio.h>
2:
3:    int add(int* c , int* d)  // 代码和前面完全一样
4:    {
0040B650   push        ebp
0040B651   mov         ebp,esp
0040B653   sub         esp,44h
0040B656   push        ebx
0040B657   push        esi
0040B658   push        edi
0040B659   lea         edi,[ebp-44h]
0040B65C   mov         ecx,11h
0040B661   mov         eax,0CCCCCCCCh
0040B666   rep stos    dword ptr [edi]
5:        int e;
6:        e=*c+*d;
0040B668   mov         eax,dword ptr [ebp+8]
0040B66B   mov         ecx,dword ptr [eax]
0040B66D   mov         edx,dword ptr [ebp+0Ch]
0040B670   add         ecx,dword ptr [edx]
0040B672   mov         dword ptr [ebp-4],ecx
7:        return e;
0040B675   mov         eax,dword ptr [ebp-4]
8:    }
0040B678   pop         edi
0040B679   pop         esi
0040B67A   pop         ebx
0040B67B   mov         esp,ebp
0040B67D   pop         ebp
0040B67E   ret

9:
10:
11:   void main()
12:   {
0040B530   push        ebp
0040B531   mov         ebp,esp
0040B533   sub         esp,4Ch
0040B536   push        ebx
0040B537   push        esi
0040B538   push        edi
0040B539   lea         edi,[ebp-4Ch]
0040B53C   mov         ecx,13h
0040B541   mov         eax,0CCCCCCCCh
0040B546   rep stos    dword ptr [edi]
13:       int a = 1;
0040B548   mov         dword ptr [ebp-4],1  // 栈中保存a 
14:       int b = 2;
0040B54F   mov         dword ptr [ebp-8],2  // 栈中保存b 
15:       int c;
16:
17:       c=add(&a,&b);
0040B556   lea         eax,[ebp-8]       // 取b的地址 
0040B559   push        eax   // 入栈作为第二个参数,
同前面的区别是这里直接入栈,
                               而前面是把p2的值入栈,显然值实际是相等的
0040B55A   lea         ecx,[ebp-4]     // 取a的地址 
0040B55D   push        ecx           // 入栈作为第一个参数
0040B55E   call        @ILT+45(add) (00401032)
0040B563   add         esp,8
0040B566   mov         dword ptr [ebp-0Ch],eax
18:
19:       while (1)
0040B569   mov         edx,1
0040B56E   test        edx,edx
0040B570   je          main+44h (0040b574)
20:       {
21:       }
0040B572   jmp         main+39h (0040b569)
22:   }
0040B574   pop         edi
0040B575   pop         esi
0040B576   pop         ebx
0040B577   add         esp,4Ch
0040B57A   cmp         ebp,esp
0040B57C   call        __chkesp (004010e0)
0040B581   mov         esp,ebp
0040B583   pop         ebp
0040B584   ret

(3)小结

可以看出,main和前面例子的区别不大,只是省去了局部变量p1和p2,入栈的还是一个地址。add函数和前面完全一样。

3.2.3 传入指针,指针指向一个结构类型

(1)源程序

#include <stdio.h>
typedef struct T_test
{
  char x;
  int y;
}T_test;

int add(T_test* c , T_test* d)
{
    int e;
    e=c->x+d->y;  
    return e;
}

void main()
{
    T_test a = {1,2};
    T_test b = {3,4};
    int c;
    T_test* p1;
    T_test* p2;

    p1=&a;
    p2=&b;
    c=add(p1,p2);

    while (1)
    {
    }
}

(2)汇编代码

1:    #include <stdio.h>
2:
3:    typedef struct T_test
4:    {
5:      char x;
6:      int y;
7:    }T_test;
8:
9:
10:   int add(T_test* c , T_test* d)
11:   {
0040B650   push        ebp
0040B651   mov         ebp,esp
0040B653   sub         esp,44h
0040B656   push        ebx
0040B657   push        esi
0040B658   push        edi
0040B659   lea         edi,[ebp-44h]
0040B65C   mov         ecx,11h
0040B661   mov         eax,0CCCCCCCCh
0040B666   rep stos    dword ptr [edi]
12:       int e;
13:       e=c->x+d->y;
0040B668   mov         eax,dword ptr [ebp+8] // 取第一个参数,
即形参c,其值等于p1
0040B66B   movsx       ecx,byte ptr [eax]    // 取c->x
0040B66E   mov         edx,dword ptr [ebp+0Ch] // 取第二个参数,
即形参d,其值等于p2
0040B671   add         ecx,dword ptr [edx+4]   // c->x+d->y
0040B674   mov         dword ptr [ebp-4],ecx   // e=c->x+d->y
14:       return e;
0040B677   mov         eax,dword ptr [ebp-4]   // e通过eax寄存器返回
15:   }
0040B67A   pop         edi
0040B67B   pop         esi
0040B67C   pop         ebx
0040B67D   mov         esp,ebp
0040B67F   pop         ebp
0040B680   ret

16:
17:
18:   void main()
19:   {
0040B530   push        ebp
0040B531   mov         ebp,esp
0040B533   sub         esp,5Ch
0040B536   push        ebx
0040B537   push        esi
0040B538   push        edi
0040B539   lea         edi,[ebp-5Ch]
0040B53C   mov         ecx,17h
0040B541   mov         eax,0CCCCCCCCh
0040B546   rep stos    dword ptr [edi]
20:       T_test a = {1,2};
0040B548   mov         byte ptr [ebp-8],1     // 栈中保存a.x
0040B54C   mov         dword ptr [ebp-4],2    // 栈中保存a.y
21:       T_test b = {3,4};
0040B553   mov         byte ptr [ebp-10h],3   // 栈中保存b.x
0040B557   mov         dword ptr [ebp-0Ch],4  // 栈中保存b.y
22:       int c;
23:       T_test* p1;
24:       T_test* p2;
25:
26:       p1=&a;
0040B55E   lea         eax,[ebp-8]
0040B561   mov         dword ptr [ebp-18h],eax   // 栈中保存指针p1,p1=&a
27:       p2=&b;
0040B564   lea         ecx,[ebp-10h]
0040B567   mov         dword ptr [ebp-1Ch],ecx   // 栈中保存指针p2,p2=&b
28:       c=add(p1,p2);
0040B56A   mov         edx,dword ptr [ebp-1Ch]
0040B56D   push        edx                  // p2的值入栈作为第二个参数
0040B56E   mov         eax,dword ptr [ebp-18h]
0040B571   push        eax                   // p1的值入栈作为第一个参数
0040B572   call        @ILT+50(add) (00401037)
0040B577   add         esp,8
0040B57A   mov         dword ptr [ebp-14h],eax
29:
30:       while (1)
0040B57D   mov         ecx,1
0040B582   test        ecx,ecx
0040B584   je          main+58h (0040b588)
31:       {
32:       }
0040B586   jmp         main+4Dh (0040b57d)
33:   }
0040B588   pop         edi
0040B589   pop         esi
0040B58A   pop         ebx
0040B58B   add         esp,5Ch
0040B58E   cmp         ebp,esp
0040B590   call        __chkesp (004010e0)
0040B595   mov         esp,ebp
0040B597   pop         ebp
0040B598   ret

(3)小结

从这个例子可以得出两个重要的启示:一是指针传递可以节省栈空间,不需要把结构的副本都入栈(可对比下面结构按值传递的例子);另一个是,编译出来的代码并没有什么地方或说标志来指明某个变量是某个结构中的字段,即看不到结构类型了,都是通过地址加上偏移量来访问的(参见add的代码注释)。这个可能出乎一些人的意料,但事实就是这样。

c语言是编译时严格类型检查的,从汇编代码看,其中已经没有任何的变量类型信息,对我们定义的结构中变量的引用最后都转化成某一个基址+偏移量的方式来进行,而并没有额外的信息用于指明某个变量是个什么类型,类型检查工作全部由编译器在编译时进行。这一点和Java不同,Java编译出的是一个中间的代码,其中可能会包含类型信息,然后把这个中间的代码再交给Java虚拟机去解释执行(可以进行变量类型检查),正是由于这点,所以Java在某种程度上可以认为是一种更安全的编程语言,但显然其编译出来的中间代码要大一点,而且后面还要一个虚拟机去解释执行。

这种应用实际也是我们代码中最常用的使用方式,看来C语言的确好,不知觉的我们就能用最好的方式来调用函数。

  打分:5.0/5 (共3人投票)

给我留言