关于C中的未定义行为,提出了一些有趣的问题here。其中一个是(略有修改)
以下代码是否会导致未定义的行为?
int i = 0, *a = &i; // Line 1 a[i] = i + 1; // Line 2
由于那里对这部分问题没有具体的答案,而且我有兴趣知道C ++中的行为,我在这里再次提出它。
来自Undefined Behavior and Sequence Points的规则#2说
此外,只能访问先前值以确定要存储的值
显然,在上面的示例中,值被访问了两次:a[i]
(lhs)和i
(rhs),并且只有其中一个(rhs)确定要存储的值。
第2行是否违反上述规则并导致C ++ 03中的未定义行为?
对于在第2行修改i
是否存在一些疑惑?
答案 0 :(得分:18)
这将导致C ++ 03中的未定义行为,以及C ++ 11中明确定义的行为。
C ++ 03:未定义的Behvaior
从C ++ 03标准第5节第4段:
在前一个和下一个序列点之间,标量对象应通过表达式的计算最多修改一次其存储值。此外,只能访问先前值以确定要存储的值。
注意第二句:i
的先前值只能用于确定要存储的值。但在这里它也用于确定数组索引。因为此分配将修改i
,a[0] = i+1
已明确定义,而a[i] = i+1
则未定义。请注意,赋值不会生成序列点:只有完整表达式的末尾(分号)才会生成。
C ++ 11:定义良好的行为:
C ++ 11摆脱了序列点的概念,而是定义了在此之前对哪些评估进行了排序。
从标准第1.9节第15段开始:
在运算符的结果的值计算之前,对运算符的操作数的值计算进行排序。如果对标量对象的副作用相对于同一标量对象的另一个副作用或使用相同标量对象的值进行的值计算未被排序,则行为未定义。
赋值运算符的两个操作数在实际赋值之前被排序。因此,我们将评估a[i]
和i+1
,然后才会修改i
。结果很明确。
答案 1 :(得分:3)
int i = 0, *a = &i;
声明之间有一个序列点,因此这里没有UB。但请注意,以这种方式声明/定义变量是一个坏主意。任何正常的编码标准都会告诉你每行声明一个变量。
a[i] = i;
i
没有任何改变,因此也没有UB。
答案 2 :(得分:2)
让我们分解表达式a[i] = i + 1
吗?
= -- [] -- a
\ \_ i
\
\_ + -- i
\_ 1
有效地,a[i]
是指&i
,但请注意,a[i]
和i+1
都不会修改i
。只有在执行i
(赋值本身)时才会修改=
。
由于在此函数生效之前需要评估任何函数的操作数,这实际上等同于:
void assign(int& address, int value) { address = value; }
assign(a[i], i + 1);
=
确实有点特别之处在于它是内置的并且不会导致函数调用,在实际赋值之前,两个操作数的评估仍然是排序的,因此在i
被修改之前首先评估它们,并且a[i]
(指向i
位置)被分配给。
答案 3 :(得分:0)
在这种情况下,只有在修改相同的内存地址而没有修改之间的序列点时才会发生未定义的行为。具体来说,C99规范,第6.5 / 2节规定,
在前一个和下一个序列点之间,一个对象应该具有它 通过表达式的评估,最多修改一次存储值。 此外,只能访问先前值以确定 值存储。
在您的情况下,在序列点之间不会修改相同的内存地址,因此没有未定义的行为。
答案 4 :(得分:0)
我想指出一点:a[i] = i
不总是导致定义明确的行为。在指定的情况下明确定义行为的原因是由于初始值i
和a
。
让我详细说明:
int i = 1, *a = &i; // Line 1, i initialized to anything other than 0
a[i] = i + 1; // Line 2, all of a sudden we are in buffer over/underflow
对于i
的任何其他初始值,我们正在访问与i
本身不同的内存位置,这会产生未定义的行为。
答案 5 :(得分:0)
不,不。第一行有一个序列点(逗号),因此它不是未定义的行为:
int i = 0, *a = &i;
第二行完全正常。
a[i] = i + 1;
由于i + 1
创建了一个临时值,i
只会在分配时修改一次。然而,这将是未定义的行为:
a[i] = i++;