printf多个自增自减表达式的底层实现原理

提起i++(i--)和++i(--i),相信大家非常熟悉。两者的区别是:前者是先赋值,然后再自增或自减;后者是先自增或自减,后赋值。但是当printf函数与多个自增自减表达式结合起来,编译器实现i++(i--)和++i(--i)的原理你真的了解吗?今天,我们从一道有意思的题目揭开printf多个自增自减表达式底层实现的面纱......

源程序

int main()
{
    int i=0;
    printf("%d,%d,%d\n",i++,--i,i++);
    return 0;
}
 
  按照我们的理解,这道题目太简单了嘛,不就是0,0,0。因为函数传参从右向左,i=0,先进行i++,i先赋值,那么取值为0,然后自增,i变成1。接着--i,先自减,i变成0,再赋值,取值为0。最后i++,先赋值,取值为0,再自增,i变成1。理完思路,你不禁会想,这道题没有一点意思,哈哈,先别高兴太早,等运行完结果验证一下......

printf多个自增自减表达式的底层实现原理_第1张图片

结果是:0 1 0,与我们分析的结果有出入,回过头再去分析我们的思路,没有问题啊!那么问题出在哪里?看来我们只知其一不知其二,不如反汇编之后,从汇编指令来分析编译器i++,++i的底层实现原理,来解决我们的问题。

汇编指令

int i=0;
00EA13BE  mov         dword ptr [i],0 
	printf("%d %d %d\n",i++,--i,i++);
00EA13C5  mov         eax,dword ptr [i] 
00EA13C8  mov         dword ptr [ebp-0D0h],eax 
00EA13CE  mov         ecx,dword ptr [i] 
00EA13D1  add         ecx,1 
00EA13D4  mov         dword ptr [i],ecx 
00EA13D7  mov         edx,dword ptr [i] 
00EA13DA  sub         edx,1 
00EA13DD  mov         dword ptr [i],edx 
00EA13E0  mov         eax,dword ptr [i] 
00EA13E3  mov         dword ptr [ebp-0D4h],eax 
00EA13E9  mov         ecx,dword ptr [i] 
00EA13EC  add         ecx,1 
00EA13EF  mov         dword ptr [i],ecx 
00EA13F2  mov         esi,esp 
00EA13F4  mov         edx,dword ptr [ebp-0D0h] 
00EA13FA  push        edx  
00EA13FB  mov         eax,dword ptr [i] 
00EA13FE  push        eax  
00EA13FF  mov         ecx,dword ptr [ebp-0D4h] 
00EA1405  push        ecx  
00EA1406  push        offset string "%d %d %d\n" (0EA573Ch) 
00EA140B  call        dword ptr [__imp__printf (0EA82BCh)] 
00EA1411  add         esp,10h 
00EA1414  cmp         esi,esp 
00EA1416  call        @ILT+310(__RTC_CheckEsp) (0EA113Bh) 
汇编指令的分析

(1)先将0,放入到变量i中。

00EA13BE  mov         dword ptr [i],0
(2)将i的值0,放入到寄存器eax,再将eax的值放入到一个地址为[ebp-0D0h]的临时变量中,临时变量中的值为0。接着又把变量i的值0,放入到寄存器ecx中,ecx加1,值为1,最后将ecx的值放入到变量i中,此时i值为1。
00EA13C5  mov         eax,dword ptr [i] 
00EA13C8  mov         dword ptr [ebp-0D0h],eax 
00EA13CE  mov         ecx,dword ptr [i] 
00EA13D1  add         ecx,1 
00EA13D4  mov         dword ptr [i],ecx 
(3)将i值放到寄存器edx中,此时edx的值为1,再将edx的值减1,edx值为0,再将edx的值放入变量i中,此时i值为0。
00EA13D7  mov         edx,dword ptr [i] 
00EA13DA  sub         edx,1 
00EA13DD  mov         dword ptr [i],edx 
(4)将变量i的值放入寄存器eax中,此时eax的值为0,将eax的值放入到地址为[edp-0D4h]的临时量中,此时临时量的值为0。接着又把变量i的值,放入到寄存器ecx中,ecx的值为0,再将ecx的值加1,放到变量i中。
00EA13E0  mov         eax,dword ptr [i] 
00EA13E3  mov         dword ptr [ebp-0D4h],eax 
00EA13E9  mov         ecx,dword ptr [i] 
00EA13EC  add         ecx,1 
00EA13EF  mov         dword ptr [i],ecx 

(5)将寄存器esp的值放入esi中

00EA13F2  mov         esi,esp 

(6)将地址为[ebp-0D0h]临时量的值0,放入到寄存器edx中,再将edx的值0压入栈中。

00EA13F4  mov         edx,dword ptr [ebp-0D0h] 
00EA13FA  push        edx  

(7)将变量i的值1,放入寄存器eax中,再将eax的值1压入栈中。

00EA13FB  mov         eax,dword ptr [i] 
00EA13FE  push        eax  
(8)将地址为[ebp-0D4h]临时量的值0,放入到寄存器ecx中,再将edx的值0压入栈中。
00EA13FF  mov         ecx,dword ptr [ebp-0D4h] 
00EA1405  push        ecx
(9)压入字符串参数,保存下一条指令的地址,进入printf函数。执行完printf函数之后,继续执行下面的指令。
00EA1406  push        offset string "%d %d %d\n" (0EA573Ch) 
00EA140B  call        dword ptr [__imp__printf (0EA82BCh)] 
00EA1411  add         esp,10h 
00EA1414  cmp         esi,esp 
00EA1416  call        @ILT+310(__RTC_CheckEsp) (0EA113Bh) 

看完汇编代码,我们的问题就解决了!

总结:原来在调用printf函数时,先自右向左遍历所有参数,同时进行参数的计算,最终自右向左压参入栈。在上面汇编指令分析的过程,(2)(3)(4)步完成参数的遍历和计算的操作,(6)(7)(8)步完成自右向左压参入栈的操作。所以对于i++(i--),编译器必须先将变量i保存到一个临时量中,保证不受参数运算的影响,再进行自加/自减操作,最终将临时量的值压入栈中。对于--i(++i),对变量i直接进行自加/自减操作,无需保存到临时量中,最终直接去i的内存中取值进行压栈。值得注意的是:如果在--i之后对i进一步进行别的操作,那么最终压栈的数据很可能不是我们在--i或者++i时存入i的内存的值。正如本题中,i++之后,i的值变为1,在进行--i时,i内存的值变为0,之后又对i进行i++,i内存的值变为1,所以最终--i压入栈中的参数是1,并非我们所想的0。



启示:当代码分析结果与实际运行结果不一致时,善用汇编代码去分析底层实现原理。








你可能感兴趣的:(C,语言)