未定义表达式

相关C语言的书籍里面说“a[i]=i++;”语句是非法的,具体如下:

 问:为什么下面的代码不可以?

        a[i] = i++;

 :子表达式i++有一个副作用,那就是它会改变i的值,由于i在同一表达式的其他地方被引用,因此会导致未定义的行为。无从判断该引用是旧值还是新值。注意,尽管在其他文献中认为这类表达式的行为是不确定行为,但C标准却强烈声明它是未定义行为。

 

由于本人对上面的问题和解释有些疑问,故编写了一份验证代码,代码如下:

 

    1 #include <stdio.h>

    2

    3 int main(int argc, char **argv){

    4         const int max=10;

    5         int a[max],i;

    6         for(i=0;i<max;){

    7                 a[i]=i++;

    8         }

    9         for(i=0;i<max;i++)

   10                 printf("a[%d]=%d\n",i,a[i]);

   11         return 0;

12 }

 

执行结果如下:

a[0]=0

a[1]=1

a[2]=2

a[3]=3

a[4]=4

a[5]=5

a[6]=6

a[7]=7

a[8]=8

a[9]=9

 

执行下来完全没问题,正如我所期望的,难道是书的知识有问题,或者是编译器旧的原因?书籍中对此做出的解释如下:

面对未定义行为的时候,包括范围内的实现定义和不确定行为,编译器可能做任何实现,其中就包括你所期望的结果。但是依赖这个实现却不明智,RogerMiller提供了看待这个问题的另一个角度:有人告诉我打篮球的时候不能抱着球跑。我拿了个篮球,抱着就跑,一点问题没有。显然他并不懂篮球。

         说到未定义行为,就需要说一下实现定义的行为、不确定的行为和未定义的行为的定义和区别。C语言标准中,这3种情况都代表了没有明确要求某个特定的构造或使用它的程序必须完成的动作的规范。C语言定义中的这种松散性是传统的,也是经过深思熟虑的,它允许编译器作者:(1)选择某些构造可以按照“硬件完成的方式”生成高效的代码;

(2)忽略某些太难准确定义、可能在良好书写的程序中没有什么实际用处的边界构造

         这3种“标准中没有准确定义”的行为定义如下:

         (1)实现定义的行为:实现必须选择某种行为,对程序不能编译失败。使用这种构造的程序并不错误,这种选择必须有文档说明,标准对此可以提供一些允许的行为供选择,也可能不强加任何特定需求。

         (2)不确定的行为:跟未定义类似,但无需提供文档

(3)未定义行为:任何事情都可以发生,标准对此没有任何要求。包含未定义行为的程序可能会便以失败、可能运行错误(crash或者运行结果出错)或者可能运行的如我们所期望的。

注:既然C语言标准对编译器面对未定义行为的实例时,没有任何强制要求该如何处理,那么编译器就可能做出未知的动作。程序中有未定义行为的代码,是相当危险的。未定义行为比你想象中的还未定义。

看几个未定义的表达式:

         (1)a[i]=i++;

         (2)i++*i++;

         (3)i=i++;

         (4)a^=b^=a^=b;

 

那怎样避免写出未定义的表达式呢?

在这里先引出“序列点”的概念,什么是序列点?

序列点是一个时间点,此刻尘埃落定,所有的副作用都已确保结束。

C语言标准中提及的序列点包括:

         (1)完整表达式,也即有“;”的语句;

         (2)||、&&、?:或者逗号操作符处;

         (3)函数调用时

 

避免未定义行为,需遵守以下原则:

         在上一个和下一个序列点之间,一个对象所保存的值至多只能被表达式的求值修改一次。而且只有在确定了保存值时刻或者变量值不变时才能访问引用这个变量的值。

这两句话有点晦涩,首先,它提到了被“前一个和后一个序列点”分隔的操作。这些操作通常与完整表达式有关。也就是上一个语句的分号开始到下一语句的分号结束。

第一句话就点出了“i++*i++;”和“i=i++;”的错误之处。两个表达式中的i值都被修改了两次。除非前一个i++和后一个i++之间有一个内部序列点隔开,例如i++&&i++,虽然这不是我们想要的,但是这个表达式是明确的,不是未定义表达式。

第二句话,点出了“a[i]=i++;”的错误之处,“a[i]=i++;”在a[i]在访问i值得时候不确定是i自增前的值还是自增后的值。但却允许“i=i+1;”,因为修改只发生在变量的最终值的存储。

结束!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值