您的位置:首页 > 其它

关于未定义行为的一些研究

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)上述都是不好的行为,旨在自虐和练习读汇编。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: