C [a[0]]=1是否产生未定义的行为?

C [a[0]]=1是否产生未定义的行为?,c,language-lawyer,c99,undefined-behavior,C,Language Lawyer,C99,Undefined Behavior,此C99代码是否产生未定义的行为 #include <stdio.h> int main() { int a[3] = {0, 0, 0}; a[a[0]] = 1; printf("a[0] = %d\n", a[0]); return 0; } #包括 int main(){ inta[3]={0,0,0}; a[a[0]]=1; printf(“a[0]=%d\n”,a[0]); 返回0; } 在语句a[a[0]]=1中,a[0]同时被读取和修改 我查看了

此C99代码是否产生未定义的行为

#include <stdio.h>

int main() {
  int a[3] = {0, 0, 0};
  a[a[0]] = 1;
  printf("a[0] = %d\n", a[0]);
  return 0;
}
#包括
int main(){
inta[3]={0,0,0};
a[a[0]]=1;
printf(“a[0]=%d\n”,a[0]);
返回0;
}
在语句
a[a[0]]=1中
a[0]
同时被读取和修改

我查看了ISO/IEC 9899的n1124草案。它说(用6.5个表达式):

在上一个序列点和下一个序列点之间,对象的存储值最多应通过表达式的计算修改一次。此外,之前的值应为只读,以确定要存储的值

它没有提到读取对象以确定要修改的对象本身。因此,此语句可能会产生未定义的行为

然而,我觉得很奇怪。这真的会产生未定义的行为吗

#include <stdio.h>

int main() {
  int a[3] = {0, 0, 0};
  a[a[0]] = 1;
  printf("a[0] = %d\n", a[0]);
  return 0;
}

(我还想知道其他ISO C版本中的这个问题。)

该值定义良好,除非
a[0]
包含一个无效数组索引的值(即,在您的代码中不是负值,并且不超过
3
)。您可以将代码更改为更具可读性和等效性的代码

 index = a[0];
 a[index] = 1;    /* still UB if index < 0 || index >= 3 */
index=a[0];
a[索引]=1;/*如果索引<0 | |索引>=3,仍为UB*/
在表达式
a[a[0]]=1中,必须首先计算
a[0]
。如果
a[0]
恰好为零,则将修改
a[0]
。但是编译器(除了不符合标准之外)无法在尝试读取其值之前更改求值顺序并修改
a[0]

此C99代码是否产生未定义的行为

#include <stdio.h>

int main() {
  int a[3] = {0, 0, 0};
  a[a[0]] = 1;
  printf("a[0] = %d\n", a[0]);
  return 0;
}
不会。它不会产生未定义的行为
a[0]
在两个变量之间只修改一次(第一个序列点位于初始值设定项
int a[3]={0,0,0}的末尾;
第二个序列点位于完整表达式
a[a[0]]=1
之后)

它没有提到读取对象以确定要修改的对象本身。因此,此语句可能会产生未定义的行为

一个对象可以被多次读取以修改其自身及其定义完美的行为。看看这个例子

int x = 10;
x = x*x + 2*x + x%5;   
引用的第二句话说:

此外,应仅读取先前值,以确定要存储的值

读取上述表达式中的所有
x
,以确定对象
x
本身的值


注意:注意问题中提到的引文有两部分。第一部分说:在上一个序列点和下一个序列点之间,通过计算表达式,对象的存储值最多修改一次,
所以这个表达像

i = i++;
属于UB(上一个和下一个序列点之间的两个修改)

第二部分说:此外,先前的值应该是只读的,以确定要存储的值,因此表达式如下

a[i++] = i;
j = (i = 2) + i;  
调用UB。在这两个表达式中,
i
仅在上一个序列点和下一个序列点之间修改一次,但最右边的
i
的读数不确定要存储在
i
中的值


在C11标准中,这已更改为

6.5表达: 如果标量对象上的副作用相对于同一标量对象上的不同副作用或使用同一标量对象的值计算的值未排序,则该行为未定义。[……]

在表达式
a[a[0]]=1中,
a[0]
只有一个副作用,索引
a[0]
的值计算在
a[a[0]]
的值计算之前排序

之前的值应为只读,以确定要存储的值

这有点模糊,造成了混乱,这也是C11抛弃它并引入新测序模型的部分原因

它想说的是:如果读取旧值的时间保证比写入新值的时间早,那么这没关系。否则就是UB。当然,在写入新值之前,必须先计算新值

(当然,我刚才写的描述会被一些人发现比标准文本更模糊!)

例如,
x=x+5
是正确的,因为不知道
x
就不可能算出
x+5
。但是
a[i]=i++
是错误的,因为计算出要存储在
i
中的新值不需要读取左侧的
i
。(分别考虑
i
的两次读取)


现在回到你的代码。我认为这是一种定义良好的行为,因为读取
a[0]
以确定数组索引保证在写入之前发生

我们不能写,除非我们决定在哪里写。直到我们读了
a[0]
之后,我们才知道该写什么。因此,读必须在写之前,因此没有UB


有人对序列点发表了评论。在C99中,此表达式中没有序列点,因此不讨论序列点。

C99在附录C中列出了所有序列点的枚举。在

a[a[0]] = 1;
因为它是一个完整的表达式语句,但是里面没有序列点。尽管逻辑规定必须首先计算子表达式
a[0]
,并且结果用于确定将值分配给哪个数组元素,但排序规则不能确保这一点。当
a[0]
的初始值为
0
时,
a[0]
在两个序列点之间读取和写入,读取的目的不是确定要写入的值。根据C996.5/2,计算表达式的行为因此是未定义的,但实际上我是这样做的