您现在的位置是:首页 > 学无止境 > C语言网站首页C语言 printf多个自增自减表达式的底层实现原理
printf多个自增自减表达式的底层实现原理
- C语言
- 2019-09-20
简介提起i++(i--)和++i(--i),相信大家非常熟悉。两者的区别是:前者是先赋值,然后再自增或自减;后者是先自增或自减,后赋值。但是当printf函数与多个自增自减表达式结合起来,编译器实现i++(i--)和++i(--i)的原理你真的了解吗?字数
1812.5
提起i++(i--)和++i(--i),相信大家非常熟悉。两者的区别是:前者是先赋值,然后再自增或自减;后者是先自增或自减,后赋值。但是当printf函数与多个自增自减表达式结合起来,编译器实现i++(i--)和++i(--i)的原理你真的了解吗?今天,我们从一道有意思的题目揭开printf多个自增自减表达式底层实现的面纱......
源程序
按照我们平时对自增自减运算符表达式的理解,我们预期的结果应该是:0,0,0
具体的理解过程是:函数参数的压栈是从右向左压的,所以先把i++产生的临时量0先压入栈,i的值变成1,再经过–i使i变成0,然后将0再压入栈,最后又将i++的临时量i=0压入栈,最后i的值是1,这样压入的结果就是0,0,0了。这种压栈过程其实是每计算出一个参数的值就压入栈。
但是实际结果却是:0,1,0
这是为什么呢?
原因是我们在调用标准库函数printf之前,对所传入的参数进行了遍历,将计算之后的结果保存在寄存器中,并当做实参压入栈中,由于CPU中寄存器的数量是有限的,所以在运算过程中使用了临时量,暂时保留变量的值。其运算的过程可以通过汇编指令来分析。
汇编指令
汇编指令的分析
(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。
启示
当代码分析结果与实际运行结果不一致时,善用汇编代码去分析底层实现原理。
转载:
感谢您对莫愁个人博客网站平台的认可,非常欢迎各位朋友分享到个人站长或者朋友圈,但转载请说明文章出处“来源莫愁个人博客 https://www.mochoublog.com/study/348.html”。
上一篇:如何使用C语言产生随机字符串
相关文章
- C语言
- 2019-09-20
提起i++(i--)和++i(--i),相信大家非常熟悉。两者的区别是:前者是先赋值,然后再自增或自减;后者是先自增或自减,后赋值。但是当printf函数与多个自增自减表达式结合起来,编译器实现i++(i--)和++i(--i)的原理你真的了解吗?今天,我们从一道有意思的题目揭开printf多个自增自减表达式底层实现的面纱......
源程序
按照我们平时对自增自减运算符表达式的理解,我们预期的结果应该是:0,0,0
具体的理解过程是:函数参数的压栈是从右向左压的,所以先把i++产生的临时量0先压入栈,i的值变成1,再经过–i使i变成0,然后将0再压入栈,最后又将i++的临时量i=0压入栈,最后i的值是1,这样压入的结果就是0,0,0了。这种压栈过程其实是每计算出一个参数的值就压入栈。
但是实际结果却是:0,1,0
这是为什么呢?
原因是我们在调用标准库函数printf之前,对所传入的参数进行了遍历,将计算之后的结果保存在寄存器中,并当做实参压入栈中,由于CPU中寄存器的数量是有限的,所以在运算过程中使用了临时量,暂时保留变量的值。其运算的过程可以通过汇编指令来分析。
汇编指令
汇编指令的分析
(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。
启示
当代码分析结果与实际运行结果不一致时,善用汇编代码去分析底层实现原理。
转载: 感谢您对莫愁个人博客网站平台的认可,非常欢迎各位朋友分享到个人站长或者朋友圈,但转载请说明文章出处“来源莫愁个人博客 https://www.mochoublog.com/study/348.html”。
上一篇:如何使用C语言产生随机字符串