为什么i=v[i+;+;]没有定义? 从C++(C++ 11)标准中,讨论评估顺序的1.1.15是下面的代码示例: void g(int i, int* v) { i = v[i++]; // the behavior is undefined }

为什么i=v[i+;+;]没有定义? 从C++(C++ 11)标准中,讨论评估顺序的1.1.15是下面的代码示例: void g(int i, int* v) { i = v[i++]; // the behavior is undefined },c++,language-lawyer,C++,Language Lawyer,如代码示例中所述,行为未定义 (注:对另一个结构稍有不同的问题的回答可能适用于此处:答案基本上是由于历史原因,而不是出于必要,该行为未定义。然而,该标准似乎暗示了未定义的某些理由-见下面的引文带墨迹的问题表示同意行为应该是未指定的,而在这个问题中,我要问的是为什么行为没有得到很好的指定。) 标准对未定义行为给出的推理如下: 如果标量对象上的副作用相对于 对同一标量对象或值计算的另一个副作用 使用同一标量对象的值,行为是未定义的 在这个例子中,我认为子表达式I++将在子表达式v[…]被求值之前被完

如代码示例中所述,行为未定义

(注:对另一个结构稍有不同的问题的回答可能适用于此处:答案基本上是由于历史原因,而不是出于必要,该行为未定义。然而,该标准似乎暗示了未定义的某些理由-见下面的引文带墨迹的问题表示同意行为应该是未指定的,而在这个问题中,我要问的是为什么行为没有得到很好的指定。)

标准对未定义行为给出的推理如下:

如果标量对象上的副作用相对于 对同一标量对象或值计算的另一个副作用 使用同一标量对象的值,行为是未定义的

在这个例子中,我认为子表达式
I++
将在子表达式
v[…]
被求值之前被完全求值,并且子表达式的求值结果是
I
(在增量之前),但
i
的值是该子表达式完全求值后的增量值。我认为在这一点上(在子表达式
i++
完全求值后),求值
v[…]
发生,然后是赋值
i=…

因此,尽管
i
的递增是毫无意义的,但我仍然认为应该对其进行定义


为什么会出现这种未定义的行为?

如果示例是
v[++I],我将分享您的论点
,但由于
i++
作为一种副作用修改了
i
,因此该值何时被修改还没有定义。标准可能会以这种或那种方式强制执行结果,但没有真正的方法知道
i
的值应该是什么:
(i+1)
(v[i+1])

在本例中,我认为子表达式I++将在子表达式v[…]被求值之前被完全求值,并且子表达式的求值结果是I(在增量之前),但I的值是该子表达式被完全求值之后的增量值

i++
中的增量必须在索引
v
之前进行评估,因此在分配给
i
之前,但是将增量值存储回内存之前不需要进行。
i=v[i++]
语句中有两个子操作可修改
i
(即,最终将导致从寄存器存储到变量
i
)。表达式
i++
相当于
x=i+1
i=x
,并且不要求两个操作都需要顺序进行:

x = i+1;
y = v[i];
i = y;
i = x;
通过该扩展,
i
的结果与
v[i]
中的值无关。在另一个扩展上,
i=x
赋值可以在
i=y
赋值之前进行,结果将是
i=v[i]

我认为在计算子表达式v[…]之前,子表达式I++将被完全计算

但你为什么这么想

这段代码之所以成为UB代码的一个历史原因是允许编译器优化在序列点之间的任何位置移动副作用。序列点越少,优化的潜在机会就越多,但程序员就越困惑。如果代码说:

a = v[i++];
本标准的目的是发出的代码可以是:

a = v[i];
++i;
这可能是两个说明,其中:

tmp = i;
++i;
a = v[tmp];
就不止两个了

a
i
时,“优化代码”中断,但标准允许进行优化,即当
a
i
时,原始代码的行为未定义

<>标准可以很容易地说,<>代码> I++< /COD>必须按照你的建议进行赋值,然后行为被完全定义,并且优化是被禁止的,但这不是C和C++如何做的。 还要注意的是,这些讨论中提出的许多例子使我们更容易辨别出UB的存在,而不是一般情况。这导致人们说应该定义行为并禁止优化是“显而易见的”。但请考虑:

void g(int *i, int* v, int *dst) {
    *dst = v[(*i)++];
}
<函数>在<代码> i!= dST < />代码时定义,在这种情况下,您希望得到所有优化(这就是为什么C99引入<代码>限制< /代码>,允许比C89或C++做更多优化)的原因。为了给你优化,当代码> i==dSt/COD>时,行为是不确定的。C和C++标准在涉及别名、程序员未预期的未定义行为和禁止某些情况下失败的期望优化之间的界限微不足道。提问者会倾向于少一点优化,多一点定义行为,但划定界限仍然不简单

除了行为是否被完全定义外,还有一个问题是它是否应该是UB,或者仅仅是与子表达式相对应的某些定义良好的操作的未指定的执行顺序。C之所以选择UB,是因为序列点的概念,以及编译器实际上不必有因此,标准没有通过说“该”值在某个未指定的点上更改来约束优化器,而是说(换言之):(1)任何依赖于之前修改过的对象的值的代码
int f(int& i0, int& i1) {
    return i0 + i1++;
}
int i = 3;
int j = f(i, i);
void foo(int v[], int i){
  i = v[i++];
}
input variable i // = 0x00000000
input variable v // = &[0xBAADF00D, 0xABABABABAB, 0x10101010]
task get_i_value: GET_VAR_VALUE<int>(i)
reg indx = WAIT(get_i_value)
task write_i++_back: WRITE(i, INC(indx))
task get_v_value: GET_VAR_VALUE<int*>(v)
reg arr = WAIT(get_v_value)
task get_v[i]_value = CALC(arr + sizeof(int)*indx)
reg pval = WAIT(get_v[i]_value)
task read_v[i]_value = LOAD_VALUE<int>(pval)
reg got_value = WAIT(read_v[i]_value)
task write_i_value_again = WRITE(i, got_value)
(discard, discard) = WAIT(write_i++_back, write_i_value_again)
a + b (value computation)
^   ^
|   |
a   b (value computation)
i = v[i++]
^     ^
|     |
i★  v[i++] = *(v + (i++))
                  ^
                  |
               v + (i++)
               ^     ^
               |     |
               v     ++ (side effect on i)★
                     ^
                     |
                     i
p = p->next = q
extern int *foo(void);
extern int *p;

*p = *foo();
*foo() = *p;
[For *p = *foo()]
call foo (which yields result in r0 and trashes r1)
load r0 from address held in r0
load r1 from address held in p
store r0 to address held in r1

[For *foo() = *p]
call foo (which yields result in r0 and trashes r1)
load r1 from address held in p
load r1 from address held in r1
store r1 to address held in r0