【转】关于C的未定义行为

关于C的未定义行为

转自:http://www.guokr.com/blog/471312/

对于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的一行算法,几乎所有的现代编译器都能正常运行。
虽然是“未定义算法”,这个也算默默的达成了一种协议了吧。
虽然,使用它们仍然是危险的。

本文由飞翔的鱼授权(果壳网)发表,文章著作权为原作者所有。

时间: 2024-11-05 13:34:53

【转】关于C的未定义行为的相关文章

预定义的类型“System.Object”未定义或未导入

生成网站或项目时,VS 2010 出现如下错误: 预定义的类型“System.Object”未定义或未导入 无效的引用选项 无法引用目录 解决方法: 关掉 VS 2010 重新打开即可.

SharePoint 2013中使用SP.UI.ModalDialog.showModalDialog时showModalDialog未定义的解决办法

本文讲述SharePoint 2013 中使用 SP.UI.ModalDialog.showModalDialog时 showModalDialog  未定义的问题. function DialogCallback(dialogResult, returnValue) { if (returnValue == '1') { alert("operation successfully"); } } var options = { url:'url', width: 600, height

IE8下提示'console'未定义错误

在开发的过程中由于调试的原因,在代码中加入console.info("xxxx"),而未进行删除 在IE8下测试该代码所在的页面报错,如下: 需要注意的是,使用console对象查看对象信息,在IE8浏览器下未打开开发人员工具(F12)的情况下 会报'console'未定义错误. 解决办法:1.打开开发人员调试工具(F12)                    2.注释掉该代码 IE8下提示'console'未定义错误

C语言学习笔记(5):未定义行为

接触到这份概念的起因是这样的,有这样一段代码: #include <stdio.h> void main() { int i = 5, j = 5, p, q; p = (i++) + (i++) + (i++); q = (++j) + (++j) + (++j); printf("%d, %d\n", p, q); } 这段代码,在VC++6.0和在线编译器codepad中编译结果是:15,22 在VS2015中的编译结果是:15,24.这说明同样的语句,在不同的编译器

编译lua5.3.2报错提示libreadline.so存在未定义的引用解决方法

从官网上下载5.3.2的源码后,make linux进行编译,提示报错: gcc -std=gnu99 -o lua lua.o liblua.a -lm -Wl,-E -ldl -lreadline /usr/local/lib/libreadline.so:对‘tputs’未定义的引用 /usr/local/lib/libreadline.so:对‘tgoto’未定义的引用 /usr/local/lib/libreadline.so:对‘tgetflag’未定义的引用 /usr/local/

SCRIPT5009: “Sys”未定义 部署.net ajax 解决方案

今天在部署asp.net ajax 的时候发现部署服务器的时候,ajax刷新不正确,开始以为是System.Web.Extensions没有引用到本地,baidu一圈发现(最近google上不了郁闷~~准备翻墙),都是改正webconfig文件若干,现记录解决方案,服务器环境windows server 2008 R2 +iis7.5 +.net4.0 1.inetmgr-〉找到对应的website,.net 映射程序,*.axd没有.net 4.0的对应项,添加之 2.重起整个应用程序连接池,

js未定义判断

if (typeof(homeType) == 'undefined') { //..... //..... } typeof函数判断,如果未定义的就会返回undefined,注意undefined 要用引号:

JSON未定义解决办法

ie6.ie7下JSON.parse JSON未定义的解决方法 解决方法一: var jsons = req.responseText; var s; if (typeof(JSON) == 'undefined'){ s = eval("("+jsons+")"); }else{ s = JSON.parse(jsons); } 解决方法二: 调用的页面里引用json2.js即可解决问题(推荐方法二). 即: <script type="text/

Flashbuilder的bug FlashBuilder 1119: 访问可能未定义的属性 on (通过 static 类型

FlashBuilder 1119: 访问可能未定义的属性 on (通过 static 类型 当此问题出现的时候 无论 刷新 清理 注释 删除 乃至重启电脑都无济于事. 解决方法:备份此类到另外一个地方. 然后删除 ,刷新. 把刚才备份的类粘贴回来. Flashbuilder的bug FlashBuilder 1119: 访问可能未定义的属性 on (通过 static 类型,码迷,mamicode.com Flashbuilder的bug FlashBuilder 1119: 访问可能未定义的

gcc/g++ 编译时出现:“对’xxxx’未定义的引用,collect2: error: ld returned 1 exit status” 的错误

出现的问题: 在使用 make 编译实现一个程序时,出现了下面的错误.查看程序源文件所在的目录时发现程序已经完成了编译,并生成了 list_repo.o 的文件,说明是在程序链接生成可执行文件时发生了问题. storages/local.o:在函数‘LocalStorage::init(std::map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >,