printf多个自增自减表达式的底层实现原理
来源:互联网 发布:网络象棋需求分析 编辑:程序博客网 时间:2024/05/20 04:09
提起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 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中。
00BC13BE mov dword ptr [i],0(2)将i的值0,放入到寄存器eax,再将eax的值放入到一个地址为[ebp-0D0h]的临时变量中,临时变量中的值为0。接着又把变量i的值0,放入到寄存器ecx中,ecx加1,值为1,最后将ecx的值放入到变量i中,此时i值为1。
00BC13C5 mov eax,dword ptr [i] 00BC13C8 mov dword ptr [ebp-0D0h],eax 00BC13CE mov ecx,dword ptr [i] 00BC13D1 add ecx,1 00BC13D4 mov dword ptr [i],ecx(3)将i值放到寄存器edx中,此时edx的值为1,再将edx的值减1,edx值为0,再将edx的值放入变量i中,此时i值为0。
00BC13D7 mov edx,dword ptr [i] 00BC13DA sub edx,1 00BC13DD mov dword ptr [i],edx(4)将变量i的值放入寄存器eax中,此时eax的值为0,将eax的值放入到地址为[edp-0D4h]的临时量中,此时临时量的值为0。接着又把变量i的值,放入到寄存器ecx中,ecx的值为0,再将ecx的值加1,放到变量i中。
00BC13E0 mov eax,dword ptr [i] 00BC13E3 mov dword ptr [ebp-0D4h],eax 00BC13E9 mov ecx,dword ptr [i] 00BC13EC add ecx,1 00BC13EF mov dword ptr [i],ecx
(5)将寄存器esp的值放入esi中
00BC13F2 mov esi,esp
(6)将地址为[ebp-0D0h]临时量的值0,放入到寄存器edx中,再将edx的值0压入栈中。
BC13F4 mov edx,dword ptr [ebp-0D0h] 00BC13FA 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。
启示:当代码分析结果与实际运行结果不一致时,善用汇编代码去分析底层实现原理。
- printf多个自增自减表达式的底层实现原理
- printf 打印多个自加自减的量,出现的意料之外的结果。
- 一个表达式中出现多个自增自减运算符的求值问题
- printf函数结合自增自减运算的使用
- printf函数中的自增和自减
- 自增自减运算的原理解析
- Android小demo,两个button,控制多个输入框实现值自增自减。
- 自增自减、关系表达式
- jvm自增,自减运行原理
- 自解压的jar实现原理
- C51单片机自实现printf函数
- printf的实现原理
- printf的实现原理
- lamda表达式的底层原理
- C语言 printf函数对参数的计算顺序自加自减的讨论
- JavaScript自增、自减运算符与表达式
- JavaScript自增、自减运算符与表达式语法
- 自增自减
- plus-one:数组模拟的大数+1
- 熊猫学猿--(第八竹--循环 for、foreach)
- 新媒行面试指南
- ftp工作原理详解
- 怎样写工科研究生论文
- printf多个自增自减表达式的底层实现原理
- Android 支持库 v26
- handler三秒轮播数据
- Android 8.1 开发者预览版 —— 为 ML 机器学习做好准备的 Oreo 升级版
- Android Oreo 后台策略
- Bitmap
- 【转】ZFS读缓存深入研究:ARC
- Effective C++ 8. Prevent exceptions from leaving destructors
- 一起来学SpringCloud之