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语言的确好,不知觉的我们就能用最好的方式来调用函数。
最新评论