关于C的未定义行为
对于C的初学者来说,被要求做下面的这种题目真的是脑残的不能再脑残的行为。但是很多C初级教程——居然都有这样的题。
最典型的例子就是
a+=a++;
这种情况下,a最后到底等于多少了?
编译器应该如何理解a+=a++呢?首先是展开,a=a+a++;
然后分别计算a和a++的值,把它们相加,然后把结果赋给a。
但是这里有一个问题,就是执行完a++之后,a++的值等于a本身的值,但是a的值却变成了a+1。
所以关键是处理顺序。
比如说int a=3;
如果编译器先计算赋值号+=左边a的值为3,然后计算右边a++的值为3,同时a变为4。
然后计算3+3=6,赋给a,那么a现在的值就是6。
如果编译器先计算赋值号右边的a++,得到的结果为3,同时a变为4,然后计算左边a=4。
接着计算4+3=7,于是7这个数被赋值给了a。
也就是说,不同的理解方法,在这个例子里面居然会得到不同的答案?
我为什么要用居然?难道这个结果不是不可思议的么?一样的表达式,只不过编译器不一样,就得出了不同的结果,这真是个悲剧啊。
难道没有什么标准要求编译器采用相同的理解模式么?C语言的标准遵从ANSI C标准。但是很不幸,ANSI C标准里面,并没有关于遇到这种情况应如何处理的规定,反而是指出,编译器你看着办吧。
这就是C语言的“未定义行为”。
话说我只是有在用一个GCD函数的时候被某大神狠狠的吐槽了,这个GCD函数如下:
int GCD(int a,int b){
while (a %= b ^= a ^= b ^= a);
return b;
}
就像上面分析的那样,这段程序在编译过程中,会出现什么顺序,这也是标准里面没有规定的,属于未定义行为。
所以用这个函数并不一定能保证得到正确的结果。
这种事情嘛。。。既然是交给编译器的。
我想这个实际上应该是为了代码优化。众所周知,C是一个十分注重效率的语言,并且有那种为了效率放弃一切的感觉,不评价这个好不好,反正人家在最受欢迎的语言排行榜第一位的宝座上坐了不知道多少年了。
给编译器更大的自由,编译器就能更好的优化生成的二进制代码。
还有其他的方面,比如越界数组。
就像这样
char str1[]="myworld";
str1[18]='\0';
数组str1哪里来的第19项啊!!!这种东西居然能通过编译!!!
使用越界数组也是C的一个“未定义行为”。C的标准没有规定编译器在碰到这种情况应该怎么做。这个时候编译器的想法应该是——多一事不如少一事,我也不检查这里到底是不是这个数组的范围了,反正你都叫我写了,我就写吧。
还有一个典型的操作就是允许一个随便指的指针的读写。
比如我申请了一个动态区域,然后释放掉了:
int *p;
p=(int *)malloc(4*sizeof(int));
/*各种对p的操作*/
free(p);
p[0]=0;
printf("%d%d%d%d",p[0],p[1],p[2],p[3]);
毫不夸张的说,我自己的程序多次死在这种地方,就是free以后再print。。。
再比如:
int *p;
p=0x1e642a80;
p[8]=24;
这种指针操作居然也给通过????
更要命的是,上面的这些都属于C的“未定义行为"就是说,虽然这些操作可以进行,但是编译器并不保证执行结果。
就是说这种东西不但不报错给通过了,而且还不按照我们想象的样子执行,而是由着编译器的性子随便来?
从这个角度看,真的是太苦逼了。
我还碰到过一段脑残代码,类似这样:
char tips[]="No";
if(condition){
strcpy(tips,"Yes");
}
对这种东西。。。。。呵呵。在Windows下运行就等着被中断吧。。
还有使用未初始化的变量也是一种“未定义行为”,比如:
int x;
printf("%d",x);
通常你也不知道你会在屏幕上看到什么。。。。
GCC的第一版编译器在碰到这种情况的时候,会在你屏幕上开始一个小游戏。(这是开发组满满的恶意啊!!嗯,一定是!!)
C的变量并不会在声明时(或第一次使用前)被初始化,这个特点饱受人们诟病。
不过ANSI C本身肯定是想通过省略这些初始化操作,来提升一点运行速度。
毕竟要初始化一个大数组或是用malloc分配的一大堆空间,还是挺费力的。。。
不过好处是……
不检查数组边界,不检查指针指向地址的情况,不检查强制类型转换是否可以进行。全靠程序员程序的自觉,这点使得C的代码效率会变得很高。
所以说,C虽然大量用于需要程序安全的场合,但是由于“未定义行为”的存在——C绝对不是一个安全的语言!!
但是更多的情况是,之所以这样,所以才会更希望用C来实现。
还有一点需要说明的是。。
最开始那段:
int a=3;
a+=a++;
几乎所有的现代编译器的结果都为7。
那个GCD的一行算法,几乎所有的现代编译器都能正常运行。
虽然是“未定义算法”,这个也算默默的达成了一种协议了吧。
虽然,使用它们仍然是危险的。