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

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

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

  1. IE10,11下_doPostBack未定义错误的解决方法

    出现的原因 .NET2.0和.NET4.0一起发布的浏览器定义文件中有一个错误,它们保存相当一部分浏览器版本的定义.但是浏览器的有些版本(比如IE10,11)则不再在这个范围之内.因此,ASP.NET ...

  2. js未定义判断

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

  3. C++中的"未定义的行为"

    2.1 位运算 位运算的运算对象是整数类型的,并且把运算对象看成是一个二进制位的集合.运算对象可以是带符号也可以是无符号.如果是带符号且值为负,那么位运算如何处理运算对象的符号位依赖于机器.而且此时的 ...

  4. IE8、IE9浏览器下报:JSON未定义 解决方法

    IE8.IE9浏览器下报:JSON未定义的问题 解决方法: 在jsp中引入如下代码 <!-- 解决 IE8.IE9 下显示混乱的问题--><% String browserStrin ...

  5. IIS7下ajax报未定义错误

    项目之前在iis6环境下运行的很好,今天在WIN7下发布,结果居然报对象未定义错误,经过个把小时折腾,终于弄清楚原委. 在web.config中关于AjaxPro的设置,在IIS7.0(WIN7中使用 ...

  6. 未定义标识符string

    “未定义标识符string”  解决方法: 头文件加上 #include <iostream>using namespace std; string是标准库的,要加std::string, ...

  7. 【caffe】未定义函数或变量caffe_

    @tag: caffe windows10上配置好caffe后(配置了matlab接口),运行caffe-master/matlab/demo/classification_demo.m报错,提示: ...

  8. gcc编译时对'xxxx'未定义的引用问题

    gcc编译时对’xxxx’未定义的引用问题 gcc编译时对’xxxx’未定义的引用问题 原因 解决办法 gcc 依赖顺序问题 在使用gcc编译的时候有时候会碰到这样的问题,编译为.o(obj) 文件没 ...

  9. JavaScript 运行时错误: 无法获取未定义或 null 一种解决方案

    脚本是肯定没有错误的!! 引用了高版本的jquery jquery-1.10.1.min.js 但在ie10下面就是报错 "JavaScript 运行时错误: 无法获取未定义或 null & ...

  10. C#引用COM对象,报错:《类型 *** 未定义构造函数, 无法嵌入互操作类型 *** 。请改用适用的接口》的解决办法。

    错误信息: 1.类型“SQLDMO.BackupClass”未定义构造函数 2.无法嵌入互操作类型“SQLDMO.BackupClass”.请改用适用的接口. 代码如下:                ...

随机推荐

  1. JavaScript高级程序设计24.pdf

    Element类型 Element类型用于表现XML或HTML元素,提供对元素标签名.子节点及特性的访问,它具有以下特征 nodeType的值为1: nodeName的值为元素的标签名: nodeVa ...

  2. linux IPC总结——管道

    管道 管道是unix ipc的最古老形式,是一种在内存中的特殊文件,只能在具有公共祖先的进程之间使用(即父子进程,兄弟进程). 管道由pipe函数创建 #include <unistd.h> ...

  3. hadoop的安装

    1. 获取linux操作系统 可以申请云主机. 2. 安装JDK,配置环境变量 sudo apt-get install openjdk-7-jdk vim /etc/profile 在配置文件中配置 ...

  4. C语言snprintf函数

    int snprintf(char *restrict buf, size_t n, const char * restrict  format, ...); 函数说明:最多从源串中拷贝n-1个字符到 ...

  5. HDU 4635 多校第四场 1004 强联通

    我还有什么好说,还有什么好说...... 我是SBSBSBSBSBSBSBSBSBSBSBSBBSBSBSBSBSBSBSBSBS........................ 题意 思路什么的都不 ...

  6. java模拟DVD管理器

    import java.util.*;import java.text.*;class DVDSet{    String[] name = new String[50]; //名字    int[] ...

  7. oracle启动

  8. OGG 单表初始化操作步骤

    有时候ogg两端数据不一致,且数据量较大,手工修改比较复杂的情况下,我们需要对这些表进行初始化.初始化的大概思路是: 1. 停止两端OGG 2. 如果业务不可以停很长时间,就需要配置目标端进程,暂停这 ...

  9. js select级联,上面分类,下面是内容

    js select级联,上面分类,下面是内容. js级联效果如下: 分类: 请选择 水果 蔬菜 其他 内容: // html和js代码如下:     <html>      <hea ...

  10. iOS开发 Xcode中的Info.plist字段含义

    Info.plist用于向iOS提供关于app,bundle或者framework的一些重要信息.它指定了比如一个应用应该怎样启动,它如何被本地化,应用的名称,要显示的图标,还有更多.Info.pli ...