C/C++拾遗录--关于一个C语言小程序的分析

虽然编了几年程序,但是对于程序到底是什么规则变成汇编代码的,在这里搞了一个小程序。用VC查看了一下汇编代码。在此之前先介绍一下关于函数运行是堆栈变化的细节。

在高级语言编写程序时,函数的调用是很常见的事情,但是在函数调用过程中堆栈的变化通常有几个细节:

1.父函数将函数的实参按照从右至左的顺序压入堆栈;

2.CPU将父函数中函数调用指令Call的下一条指令地址EIP压入堆栈;

3.父函数通过Push Ebp指令将基址指针EBP的值压入堆栈,并通过Mov Ebp,Esp指令将当前堆栈指针Esp值传给Ebp;

4.通过Sub Esp,m(m是字节数)指令可以为存放函数中的局部变量开辟内存。函数在执行的时候如果需要访问实参或局部变量,都可以通过EBP指针来指引完成。

windows系统下常用的函数调用通常有种,__cdecl和__stdCall。

1.在VC、.net等开发环境中,编写命令行程序时的Main或者_tmain函数,以及大家自己定义的很多函数都是默认采用__cdecl调用方式;

2.通过MFC编写图形界面程序的时候,其主函数声明为extern "C" int WINAPI tWinMain(参数),该函数的调用约定是__stdCall。WINAPI和PASCAL等都是__stdCall的宏定义,是一个意思,此外,大家平时调用的API函数,绝大多数都是采用__staCall的调用方式;

3.__cdecl调用方式的函数,父函数在调用子函数的时候,先将子函数的实参按照从右至左的顺序压入堆栈中,子函数返回后,父函数通过Sub Esp,n(n=函数实参个数*4)指令来恢复堆栈;

4.__stdCall调用约定函数,子函数调用时实参入栈顺序也是从左到右,但是堆栈恢复是子函数返回时自己通过Ret n指令来完成的。

下边就是针对这些知识进行的部分实践:

#include<stdio.h>
#include<windows.h>
#include<stdlib.h>
int fun(char *szIn, int nTest)
{
	char szBuf[9];
	printf("%d\n",nTest);
	strcpy(szBuf,szIn);
	return 0;
}
int main(int argc, char *argv[])
{
	char sz_In[] = "1234567";
	fun(sz_In,888);
	return 0;
}

汇编代码

00401003   int         3
00401004   int         3
@ILT+0(?fun@@YAHPADH@Z):
00401005   jmp         fun (00401020)    //进入fun函数
@ILT+5(_main):
0040100A   jmp         main (00401080)    //进入main函数,该位置是整段代码的入口
0040100F   int         3
00401010   int         3
00401011   int         3
00401012   int         3
00401013   int         3
00401014   int         3
00401015   int         3
00401016   int         3
00401017   int         3
00401018   int         3
00401019   int         3
0040101A   int         3
0040101B   int         3
0040101C   int         3
0040101D   int         3
0040101E   int         3
0040101F   int         3
--- c:\project\heap1\heap1.cpp  --------------------------------------------------------------------------------------------------------------------------------------
1:    #include<stdio.h>
2:    #include<windows.h>
3:    #include<stdlib.h>
4:    int fun(char *szIn, int nTest)
5:    {
00401020   push        ebp
00401021   mov         ebp,esp               //保存基址指针,并将现在的栈顶保存为基址指针。
00401023   sub         esp,4Ch               //腾出一部分堆栈区用于存放局部变量。
00401026   push        ebx
00401027   push        esi
00401028   push        edi                   //保存三个寄存器的值。
00401029   lea         edi,[ebp-4Ch]
0040102C   mov         ecx,13h
00401031   mov         eax,0CCCCCCCCh
00401036   rep stos    dword ptr [edi]       //将腾出的4Ch的空间初始化值为0xCC。 
6:        char szBuf[9];
7:        printf("%d\n",nTest);
00401038   mov         eax,dword ptr [ebp+0Ch]
0040103B   push        eax
0040103C   push        offset string "%d\n" (0042201c)    //先后压入栈中两个地址,nTest,一个是一个字符串指针。
00401041   call        printf (004011d0)                            //调用printf函数时,它会自动做到堆栈平衡。
00401046   add         esp,8                                        //由于刚才压入和两个参数,所以在这里手动将两个参数弹出堆栈
8:        strcpy(szBuf,szIn);
00401049   mov         ecx,dword ptr [ebp+8]                       
0040104C   push        ecx                           //压入szIn的指针。这个参数在高出基址的8位处,也就是调用该函数前压入栈中的。
0040104D   lea         edx,[ebp-0Ch]
00401050   push        edx                 //压入szBuf的指针,这个函数在低于基址的OCh位处,这是调用函数后分配的。局部变量的分配大
00401051   call        strcpy (004010e0)   //小都是按4的倍数分配的,所以尽管szBuf[9]但是也分配在了0Ch处。
00401056   add         esp,8
9:        return 0;
00401059   xor         eax,eax             //返回值在EAX中。
10:   }
0040105B   pop         edi
0040105C   pop         esi
0040105D   pop         ebx                 //弹出保存的数据。
0040105E   add         esp,4Ch             //消除为局部变量腾出的空间。
00401061   cmp         ebp,esp
00401063   call        __chkesp (00401250) //检验是否在用户自定义汇编代码中修改了ebp和esp的相对关系。一般情况下EBP>ESP
00401068   mov         esp,ebp             //将原基址恢复给栈顶寄存器。
0040106A   pop         ebp                 //弹出原调用函数的堆栈基址。            
0040106B   ret                             //函数返回。 
--- No source file  --------------------------------------------------------------------------------------------------------------------------------------------------
0040106C   int         3
0040106D   int         3
0040106E   int         3
0040106F   int         3
00401070   int         3
00401071   int         3
00401072   int         3
00401073   int         3
00401074   int         3
00401075   int         3
00401076   int         3
00401077   int         3
00401078   int         3
00401079   int         3
0040107A   int         3
0040107B   int         3
0040107C   int         3
0040107D   int         3
0040107E   int         3
0040107F   int         3
--- c:\project\heap1\heap1.cpp  --------------------------------------------------------------------------------------------------------------------------------------
11:   int main(int argc, char *argv[])
12:   {
00401080   push        ebp
00401081   mov         ebp,esp                       //保存堆栈基址
00401083   sub         esp,48h                       //腾出局部变量空间  
00401086   push        ebx
00401087   push        esi
00401088   push        edi                           //保存3个寄存器  
00401089   lea         edi,[ebp-48h]
0040108C   mov         ecx,12h
00401091   mov         eax,0CCCCCCCCh                //初始化局部变量空间
00401096   rep stos    dword ptr [edi]
13:       char sz_In[] = "1234567";
00401098   mov         eax,[string "1234567" (00422020)]
0040109D   mov         dword ptr [ebp-8],eax
004010A0   mov         ecx,dword ptr [string "1234567"+4 (00422024)]
004010A6   mov         dword ptr [ebp-4],ecx         //将字符串通过寄存器将字符拷贝到分配的空间中。
14:       fun(sz_In,888);
004010A9   push        378h                         
004010AE   lea         edx,[ebp-8]
004010B1   push        edx      //从右至左将参数压入堆栈中,数字直接压入数值,字符串则压入字符串指针
004010B2   call        @ILT+0(fun) (00401005)
004010B7   add         esp,8                        //恢复堆栈
15:       return 0;
004010BA   xor         eax,eax                      //返回值在EAX中
16:   }
004010BC   pop         edi
004010BD   pop         esi
004010BE   pop         ebx                          //恢复3个寄存器
004010BF   add         esp,48h                      //清除局部变量空间
004010C2   cmp         ebp,esp
004010C4   call        __chkesp (00401250)          //检测堆栈指针与堆栈基址   
004010C9   mov         esp,ebp                      //恢复调用函数的栈顶
004010CB   pop         ebp                          //恢复调用函数的堆栈基址
004010CC   ret                                      //函数返回
--- No source file  --------------------------------------------------------------------------------------------------------------------------------------------------
004010CD   int         3
004010CE   int         3

关于这个程序的堆栈使用情况也做了一下分析,如图:

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值