printf函数结合自增自减运算的使用

本文详细解析了C语言中自增自减运算符在printf函数中的执行顺序及结果,通过汇编语言展示了参数计算与压栈的具体过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

源程序

int main()
{
    int i = 0;
    printf("%d,%d,%d\n",i++,--i,i++);
    return 0;
}

按照我们平时对自增自减运算符表达式的理解,我们预期的结果应该是:0,0,0

具体的理解过程是:函数参数的压栈是从右向左压的,所以先把i++产生的临时量0先压入栈,i的值变成1,再经过–i使i变成0,然后将0再压入栈,最后又将i++的临时量i=0压入栈,最后i的值是1,这样压入的结果就是0,0,0了。这种压栈过程其实是每计算出一个参数的值就压入栈。

但是实际结果却是:0,1,0

这是为什么呢?

原因是我们在调用标准库函数printf之前,对所传入的参数进行了遍历,将计算之后的结果保存在寄存器中,并当做实参压入栈中,由于CPU中寄存器的数量是有限的,所以在运算过程中使用了临时量,暂时保留变量的值。其运算的过程可以通过汇编指令来分析。

汇编程序

    int i = 0;
0030526E  mov         dword ptr [i],0 
    printf("%d,%d,%d\n",i++,--i,i++);//0,1,0
00305275  mov         eax,dword ptr [i] 
00305278  mov         dword ptr [ebp-0D0h],eax 
0030527E  mov         ecx,dword ptr [i] 
00305281  add         ecx,1 
00305284  mov         dword ptr [i],ecx 
00305287  mov         edx,dword ptr [i] 
0030528A  sub         edx,1 
0030528D  mov         dword ptr [i],edx 
00305290  mov         eax,dword ptr [i] 
00305293  mov         dword ptr [ebp-0D4h],eax 
00305299  mov         ecx,dword ptr [i] 
0030529C  add         ecx,1 
0030529F  mov         dword ptr [i],ecx 
003052A2  mov         esi,esp 
003052A4  mov         edx,dword ptr [ebp-0D0h] 
003052AA  push        edx  
003052AB  mov         eax,dword ptr [i] 
003052AE  push        eax  
003052AF  mov         ecx,dword ptr [ebp-0D4h] 
003052B5  push        ecx  
003052B6  push        offset string "%d,%d,%d\n" (30AC44h) 
003052BB  call        dword ptr [__imp__printf (30E43Ch)] 
003052C1  add         esp,10h 
003052C4  cmp         esi,esp 
003052C6  call        @ILT+695(__RTC_CheckEsp) (3012BCh) 

代码解析

1、先将0存放到变量i中,再把i的值存放到寄存器eax中。

0030526E  mov         dword ptr [i],0 
00305275  mov         eax,dword ptr [i] 

2、将寄存器的值存放到地址为ebp-0D0h的临时量中,此临时量保存的值是0

00305278  mov         dword ptr [ebp-0D0h],eax 

3、又将i的值存放到寄存器ecx中,并对寄存器ecx的值进行+1操作。将寄存器ecx的值重新写到变量i中。即修改i变量的值为1。

0030527E  mov         ecx,dword ptr [i] 
00305281  add         ecx,1 
00305284  mov         dword ptr [i],ecx 

4、将i的值取出来放到寄存器edx中,并对edx寄存器进行-1操作。将运算结果重新放到变量i中,此时i的值为0。

00305287  mov         edx,dword ptr [i] 
0030528A  sub         edx,1 
0030528D  mov         dword ptr [i],edx 

5、把i的值放到寄存器eax中。然后将eax的值存放到地址为ebp-0D4h的内存中,此临时量的值是0。

00305290  mov         eax,dword ptr [i] 
00305293  mov         dword ptr [ebp-0D4h],eax 

6、将i的值放到寄存器ecx中,并对寄存器ecx进行+1操作。将ecx的值重新写回变量i中,最后i的值是1。

00305299  mov         ecx,dword ptr [i] 
0030529C  add         ecx,1 
0030529F  mov         dword ptr [i],ecx 

7、将寄存器esp(栈顶指针)的值放到esi中。

003052A2  mov         esi,esp 

8、将临时量ebp-0D0h的值放到edx中,并将edx的值压入栈中。

003052A4  mov         edx,dword ptr [ebp-0D0h] 
003052AA  push        edx  

9、将i的值取出来放入寄存器eax中,并将eax的值压入栈中。

003052AB  mov         eax,dword ptr [i] 
003052AE  push        eax  

10、将临时量ebp-0D4h的值放到寄存器ecx中,并将ecx的值压入栈中。

003052AF  mov         ecx,dword ptr [ebp-0D4h] 
003052B5  push        ecx  

11、压入字符串参数。

003052B6  push        offset string "%d,%d,%d\n" (30AC44h) 

12、保存下一条指令的地址,开始执行printf函数。

003052BB  call        dword ptr [__imp__printf (30E43Ch)] 

13、执行完printf函数之后,继续执行下面的指令。

003052C1  add         esp,10h 
003052C4  cmp         esi,esp 
003052C6  call        @ILT+695(__RTC_CheckEsp) (3012BCh) 

总结:在调用printf函数时,变量的压参是从右向左开始的,但先需要遍历函数参数,计算出每个参数具体数值。而在遍历参数时,分别进行了两次后置++和一次前置–运算,后置++会产生临时量。遍历完之后,计算出来i的值是1,但在此过程中首先将两次i的值先保存到了临时量中。所以最后实参的值有两次是从临时空间中取值,一次从i中取值。将取到的值分别存放到寄存器,再通过寄存器压入到栈中。后置++压入的参数值是临时量中的值,前置–压入的参数值是最后i的值。因此最后传入的参数分别是0,1,0,打印的结果也是如此。

可以看到函数压入的参数是字面值常量,如果有多个参数传递时,首先会遍历每个参数,计算并取出数值(有的保存在临时量中,有的则是在变量中),然后才是从右向左进行压栈。从汇编程序的执行过程中,可以看到参数的计算过成主要在第2、5、6步,参数的压栈过程是在第8 、9、10步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值