a[i] = i++ 到底对不对?

时间:2022-07-22
本文章向大家介绍a[i] = i++ 到底对不对?,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

编程中有时会遇到一些有歧义的表达式,比如 a[i] = i++ 。那么 a[i] = i++ 到底对不对呢?

首先请看如下代码:

#include<stdio.h>
int main(){
  int i=0;
  int a[3] = {1,2,3};
  a[i] = i++;
  printf("%d n",a[i]);
  printf("%d %d %d ",a[0],a[1],a[2]);
  return 0;
}

对于这个表达式中 a[i] = i++,子表达式i++有一个副作用,它会改变i的值,由于i在同一表达式中会被引用,因此这样会导致未定义的行为。因为无法判定该引用(该公式中的左边的a[i]中)是新值还是旧值。

不同的编译器在解释此类行为的时候会有不同的理解,比如下面三个编译器(dev c++ 、 codeblocks、vs2019 )对于上述的代码就有不同的理解。

从上面的运行的截图可以观察到相同的一段代码,dev c++和codeblocks的执行结果是相同的,但是vs2019和它们却并不相同。

对于此类行为,尽管有些文献中认为这类表达式的行为是不确定的,但是c标准却强烈声明它是未定义的。

未定义行为的其他示例包括访问超出其边界的数组, 解除引用空指针, 在生命终结后访问对象 或写作 据称聪明的表达 喜欢 i++ + ++i

未定义的行为还有两个不那么危险的兄弟, 不确定的行为实现定义的行为。

那么实现定义的行为、不确定的行为、未定义的行为这三者的区别在哪里呢?

首先这三种情况都代表了c语言标准中没有明确要求某个特定构造或使用它的程序必须完成的事情的领域。c语言定义中的这种松散性是传统的,但是这种规定方式是经过深思熟虑的,这种定义方式允许作者:

1 选择某些构造可以按照“硬件完成的方式”生成高效的代码。

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

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

1 实现定义的行为

抽象机的某些方面和操作在本国际标准中描述为 实现定义 (例如, sizeof(int))。这些构成了抽象机器的参数。每个实施应包括描述其在这些方面的特征和行为的文件。

2 不确定的行为

抽象机的某些其他方面和操作在本国际标准中描述为 不明 (例如,评估函数参数的顺序)。在可能的情况下,C语言国际标准定义了一组允许的行为。这些定义了抽象机器的非确定性方面。

3 未定义的行为

任何事情都有可能发生,标准对此没有任何要求,程序可能编译失败、运行错误(直接崩溃或者生成错误的结果)或者幸运的如程序员所愿。

既然标准对编译器没有进行任何要求,那么编译器就可以做出任何可能的行为。在程序中忍受未定义的想法是极其危险的,未定义行为比你想象的还要未定义。

如果大家想书写可移植代码,那么上述的三种行为都是需要极力避免的。因此我们在编写代码时最好避免 a[i] = i++ 这种C语言未定义的写法。