关于未定义行为的一些研究
2010-05-21 21:27
302 查看
#include <stdio.h> int main() { int b; printf("%d,%d/n",++b,b++); return 0; }
运行结果取决于未定义行为的一些不良写法,可总有人拿出来当题考别人。。。
首先printf()函数里面参数的求值顺序,标准C里只规定了四个运算符的求值顺序,||,&&,?:,还有逗号,但不是函数参数表里的逗号。
基于任何关于函数参数求值顺序的假设都是危险的行为。
至于printf的求值顺序在VC6.0和Gcc里面都是从右到左的。
所以第一个printf的输出两个编译器是相同的?NO。。。
还有一个++和--运算符的问题。。。。
在VC6.0中的输出是
3,2
在GCC中的输出是
4,2
分别进行反汇编,观察一下就明了了,VC6.0中的disassembly
4: int b; 5: b=2; 0040D738 mov dword ptr [ebp-4],2 //立即数2放入地址为ebp-4的内存中 6: printf("%d,%d/n",++b,b++); 0040D73F mov eax,dword ptr [ebp-4] //ebp-4内存中的值,现在是2,放入寄存器eax中 0040D742 mov dword ptr [ebp-8],eax //寄存器eax中的值放入ebp-8内存中全是2 0040D745 mov ecx,dword ptr [ebp-8] //ebp-8内存中的值放入寄存器ecx中,仍然是2 0040D748 push ecx //寄存器ecx中的值压入栈中,第一个参数,b++,没有进行任何操作就压入栈中 0040D749 mov edx,dword ptr [ebp-4] //ebp-4内存中的值放入寄存器edx中,2 0040D74C add edx,1 //edx中的值+1了 0040D74F mov dword ptr [ebp-4],edx //edx的值放入ebp-4的内存中,现在是3 0040D752 mov eax,dword ptr [ebp-4] //ebp-4的内存中的值放入eax中,全是3 0040D755 push eax //第二个参数入栈了,++b 0040D756 push offset string "%d/n" (0042201c) //第三个参数入栈 0040D75B mov ecx,dword ptr [ebp-4] //ebp-4的内存中的值放入ecx中,是3 0040D75E add ecx,1 //ecx中的值+1了,b++操作,为4 0040D761 mov dword ptr [ebp-4],ecx //ecx中的值放入ebp-4中 0040D764 call printf (00401090) 0040D769 add esp,0Ch
可以明白地址为ebp-4的内存就是存储变量b的地方,传参的时候,先做了一份拷贝放入eax中,然后又拷贝到ebp-8,然后ebp-8又拷贝到ecx,最后将ecx压入栈中。然后将b的值拷贝到edx,进行++b,操作完之后更新b的值,然后把更新过的值拷贝到eax中,并且入栈。
所有参数入栈之后,将b的值再放入ecx中,进行b++,然后更新b的值,然后调用函数。。。
实际上的结果同
int b,a;
a=b=2;
++b;
printf("%d,%d/n",b,a);
a++;
然后是GCC,GCC中的反汇编器的结果是
movl $2, -8(%ebp) //b=2 movl -8(%ebp), %eax //b拷贝到eax中 incl -8(%ebp) //自增了,经实验是b++,b=3 incl -8(%ebp) //继续自增 ++b, b=4 subl $4, %esp pushl %eax //b++对应的参数入栈,为2 pushl -8(%ebp) //++b对应的参数入栈,为4 pushl $.LC0 call printf addl $16, %esp movl $0, %eax movl -4(%ebp), %ecx leave
汇编代码比VC的简单一些
继续,语句改成
printf("%d,%d/n",b++,++b);
结果是多少呢
VC6.0中为
3,3
GCC中为
3,4
VC6.0的反汇编结果
6: printf("%d,%d/n",b++,++b); 0040D73F mov eax,dword ptr [ebp-4] //b的值放入eax中 0040D742 add eax,1 //eax中的值++,++b操作 0040D745 mov dword ptr [ebp-4],eax //写回b 0040D748 mov ecx,dword ptr [ebp-4] //b放入ecx,值为3 0040D74B push ecx //++b对应的参数入栈 0040D74C mov edx,dword ptr [ebp-4] //b的值放入edx中 0040D74F mov dword ptr [ebp-8],edx //edx的值放入ebp-8内存地址中 0040D752 mov eax,dword ptr [ebp-8] //放入eax 0040D755 push eax //b++对应的参数入栈,值为3 0040D756 push offset string "%d/n" (0042201c) 0040D75B mov ecx,dword ptr [ebp-4] 0040D75E add ecx,1 //b++操作 0040D761 mov dword ptr [ebp-4],ecx //更新b 0040D764 call printf (00401090) 0040D769 add esp,0Ch
不管b++的位置在左边还是右边,在VC6.0中是全部参数入栈之后调用函数之前更新b的值
在GCC中的反汇编结果
main: leal 4(%esp), %ecx andl $-16, %esp pushl -4(%ecx) pushl %ebp movl %esp, %ebp pushl %ecx subl $20, %esp movl $2, -8(%ebp) //b=2 incl -8(%ebp) //++b movl -8(%ebp), %eax //++b之后的结果放入eax中,值为3 incl -8(%ebp) //b++ subl $4, %esp pushl -8(%ebp) //++b入栈,值为4 pushl %eax //b++入栈,值为3 pushl $.LC0 call printf addl $16, %esp movl $0, %eax movl -4(%ebp), %ecx leave leal -4(%ecx), %esp ret
在GCC中,b++作为参数,是在保存了b更新前的值之后,就立刻进行b++。。。当然是在参数入栈之前
printf("%d,%d",b++,b++);的结果呢
利用上面的分析
VC6.0中应该是2,2
GCC中应该是3,2
事实也的确是这样的。。。。
再来一个问题,
int b=2;
int a1=++b+(++b);
a1的值是多少?
a2=++b+(b++)呢?
a3=b+++(++b)呢?
VC6.0中
a1的值为8
a2的值为6
a3的值为6
a2为例,反汇编器结果,实际上a3的结果也是一样的,优先计算++b么??
5: b=2; 0040D738 mov dword ptr [ebp-8],2 6: // printf("%d,%d/n",b++,b++); 7: a=(++b)+(b++); 0040D73F mov eax,dword ptr [ebp-8] //b的值放入eax中 0040D742 add eax,1 //++b 0040D745 mov dword ptr [ebp-8],eax //更新b 0040D748 mov ecx,dword ptr [ebp-8] //b的值拷贝到ecx中 0040D74B add ecx,dword ptr [ebp-8] //两个相同的b相加 0040D74E mov dword ptr [ebp-4],ecx //结果给a 0040D751 mov edx,dword ptr [ebp-8] //b++ 0040D754 add edx,1 0040D757 mov dword ptr [ebp-8],edx
在GCC中结果是一样的。。。
结论
1)在VC6.0中,不管b++的位置在左边还是右边,全部参数入栈之后,调用函数之前,b++才会进行。
2)在GCC: (GNU) 4.2.4中,b++作为参数,是在保存了b更新前的值之后,就进行b++操作,并更新b的值,是在参数入栈之前。
3)上述都是不好的行为,旨在自虐和练习读汇编。
相关文章推荐
- 关于ios抽屉式导航的一些研究
- 关于原生js的一些研究
- 关于nosql数据库的一些研究
- 关于可持久化线段树的一些研究(链表实现)
- 关于QT中显示中文的一些研究
- 每个 C 程序员都应知道的关于未定义行为的那点事(上篇)
- Ext研究(1)——关于Function类的一些新方法的使用
- 关于原生js的一些研究
- 关于浏览器回退默认行为的研究 回退保存页面状态
- 关于windowless窗体设计的研究---借鉴一些directui的做法
- 关于QT中显示中文的一些研究
- 关于C语言可变参数函数的一些研究和总结
- 【HEVC学习与研究】8.关于GOP与RAP的一些解释
- 关于String 连接的一些研究
- 关于Python的编码、乱码以及Unicode的一些研究
- 关于Ajax无刷新分页技术的一些研究 c#
- 行为识别笔记:关于行为检测/识别问题的分类及研究进展的讨论
- 基于gprs关于sms和mms的一些研究。
- 每个 C 程序员都应知道的关于未定义行为的那点事(中篇)
- memcached的一些研究(关于memcached的内存分配机制)