Mas*_*ara 56 c c99 undefined-behavior language-lawyer
这个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;
}
Run Code Online (Sandbox Code Playgroud)
在声明中a[a[0]] = 1;,a[0]都是读取和修改.
我看了ISO/IEC 9899的n1124草案.它说(在6.5表达式中):
在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的计算来修改一次.此外,先前的值应该只读以确定要存储的值.
它没有提到读取对象来确定要修改的对象本身.因此,此语句可能会产生未定义的行为.
但是,我觉得很奇怪.这实际上是否会产生未定义的行为?
(我也想知道其他ISO C版本中的这个问题.)
M.M*_*M.M 52
先前的值应该只读以确定要存储的值.
这有点模糊,引起了混乱,这也是C11将其抛弃并引入新的测序模型的部分原因.
它试图说的是:如果保证读取旧值的时间早于写入新值,那么这很好.否则就是UB.当然,要求在写入之前计算新值.
(当然,我刚刚写的描述会被一些人发现比标准文本更模糊!)
例如x = x + 5是正确的,因为没有x + 5事先知道就无法解决问题x.然而a[i] = i++是错误的,因为i为了计算出存储的新值,不需要在左侧读取i.(两个读数i分别考虑).
现在回到你的代码.我认为这是明确定义的行为,因为a[0]为了确定数组索引的读取保证在写入之前发生.
在我们确定在哪里写之前,我们不能写.在我们阅读之后,我们不知道在哪里写a[0].因此,读取必须在写入之前进行,因此没有UB.
有人评论了序列点.在C99中,此表达式中没有序列点,因此序列点不会进入此讨论.
hac*_*cks 16
这个C99代码是否会产生未定义的行为?
不会.它不会产生不确定的行为.a[0]在两个序列点之间仅修改一次(第一个序列点位于初始化程序的末尾,int a[3] = {0, 0, 0};第二个序列点位于完整表达式之后a[a[0]] = 1).
它没有提到读取对象来确定要修改的对象本身.因此,此语句可能会产生未定义的行为.
可以多次读取对象以修改自身及其完美定义的行为.看看这个例子
int x = 10;
x = x*x + 2*x + x%5;
Run Code Online (Sandbox Code Playgroud)
报价的第二个陈述说:
此外,先前的值应该只读以确定要存储的值.
x读取上述表达式中的所有内容以确定对象x本身的值.
注意:请注意,问题中提到的引用分为两部分.第一部分说:在上一个和下一个序列点之间,一个对象的表达式的评估最多只能修改一次.,
因此表达就像
i = i++;
Run Code Online (Sandbox Code Playgroud)
来自UB(前一个和下一个序列点之间的两个修改).
第二部分说:此外,先前的值应该是只读的,以确定要存储的值.,因此表达式如
a[i++] = i;
j = (i = 2) + i;
Run Code Online (Sandbox Code Playgroud)
调用UB.在两个表达式i中,只在前一个和下一个序列点之间修改一次,但最右边的读数i不确定要存储的值i.
在C11标准中,这已经改为
如果相对于对同一标量对象的不同副作用或使用相同标量对象的值进行值计算,对标量对象的副作用未被排序,则行为未定义.[...]
在表达式中a[a[0]] = 1,只有一个副作用,a[0]并且索引a[0]的值计算在值计算之前被排序a[a[0]].
Joh*_*ger 13
C99列出了附件C中所有序列点的列举.最后有一个
a[a[0]] = 1;
Run Code Online (Sandbox Code Playgroud)
因为它是一个完整的表达式语句,但里面没有序列点.尽管逻辑规定a[0]必须首先计算子表达式,并且结果用于确定赋值的数组元素,但排序规则并不能确保它.当初始值为a[0]is时0,a[0]在两个序列点之间读取和写入,并且读取不是为了确定要写入的值.根据C99 6.5/2,评估表达式的行为因此未定义,但在实践中我认为您不必担心它.
在这方面,C11更好.第6.5节第(1)段说
表达式是操作符和操作数的序列,其指定值的计算,或指定对象或函数,或者生成副作用,或执行其组合.在运算符的结果的值计算之前,对运算符的操作数的值计算进行排序.
请特别注意第二句,它在C99中没有类似物.你可能认为这就足够了,但事实并非如此.它适用于值计算,但它没有说明相对于值计算的副作用的排序.更新左操作数的值是副作用,因此不会直接应用额外的句子.
尽管如此,C11仍然为我们提供了,因为赋值运算符的规范提供了所需的排序(C11 6.5.16(3)):
[...]在左右操作数的值计算之后,对更新左操作数的存储值的副作用进行排序.对操作数的评估是不确定的.
(相比之下,C99只是说更新左操作数的存储值发生在前一个和下一个序列点之间.)将6.5和6.5.16组合在一起,然后,C11给出一个明确定义的序列:内部[]在之前被评估外部[],在更新存储值之前进行评估.这满足C11的6.5(2)版本,所以在C11中,定义了评估表达式的行为.
该值定义良好,除非a[0]包含的值不是有效的数组索引(即在您的代码中不是负数且不超过3).您可以将代码更改为更具可读性和等效性
index = a[0];
a[index] = 1; /* still UB if index < 0 || index >= 3 */
Run Code Online (Sandbox Code Playgroud)
在表达式中a[a[0]] = 1,首先需要进行评估a[0].如果a[0]恰好为零,那么a[0]将被修改.但是,a[0]在尝试读取其值之前,编译器(不遵守标准)无法改变评估顺序和修改.