post和pre-increment运算符的以下哪种组合在C中具有未定义的行为?

cla*_*aws 4 c expression operators undefined-behavior

我读过,有没有人可以解释这些未定义的行为(i = i ++ + ++ i,i = i ++等...)并在浪费超过2小时后尝试理解 "comp.lang.c FAQ"中的序列点时间试图通过gcc编译器解释以下结果.

expression(i=1;j=2)     i       j       k
k = i++ + j++;          2       3       3
k = i++ + ++j;          2       3       4
k = ++i + j++;          2       3       4
k = ++i + ++j;          2       3       5

k = i++ + i++;          3               2
k = i++ + ++i;          3               4
k = ++i + i++;          3               4
k = ++i + ++i;          3               6

i = i++ + j++;          4       3
i = i++ + ++j;          5       3
i = ++i + j++;          4       3
i = ++i + ++j;          5       3

i = i++ + i++;          4
i = i++ + ++i;          5
i = ++i + i++;          5
i = ++i + ++i;          6
Run Code Online (Sandbox Code Playgroud)

题:

  1. 我想知道上图中显示的所有表达式(在4组中)是否有未定义的行为?如果只有一些具有未定义的行为,那些行为和哪些不行?

  2. 对于已定义的行为表达式,请显示(不解释) 编译器如何评估它们.只是为了确保,如果我正确地获得了这个预增量和后增量.

背景:

今天,我参加了校园面试,在那里我被要求解释i++ + ++i给定值的结果i.在gcc中编译该表达式后,我意识到我在采访中给出的答案是错误的.我决定将来不会犯这样的错误,因此,尝试编译前后增量运算符的所有可能组合,并在gcc中编译它们然后尝试解释结果.我挣扎了2个多小时.我找不到评估这些表达式的单一行为.所以,我放弃了,转向stackoverflow.经过一点点阅读档案,发现有类似sequence point和未定义的行为.

P.P*_*.P. 8

除第一组外,其他三组中的所有表达式都具有未定义的行为.

如何评估定义的行为(第1组):

i=1, j=2;

k=i++ + j++; // 1 + 2 = 3
k=i++ + ++j; // 1 + 3 = 4
k=++i + ++j; // 2 + 3 = 5
k=++i + j++; // 2 + 2 = 4
Run Code Online (Sandbox Code Playgroud)

这是相当直接的.后增量与预增量之比.

在第2组和第4组中,很容易看到未定义的行为.

第2组具有未定义的行为,因为=运算符不引入序列点.


Kei*_*son 5

这些语句没有序列点。它们之间有序列点。

如果您在连续序列点之间修改同一对象两次(在本例中,通过=或通过 prefix 或 postfix ++),则行为未定义。因此,第一组 4 条语句的行为已明确定义;其他人的行为是不确定的。

如果定义了行为,则i++生成的先前i,并作为副作用i通过添加1到它来进行修改。 通过添加来++i修改,然后产生修改后的值。i1


Joh*_*ode 5

我想知道上图中显示的所有表达式(分 4 组)是否都有未定义的行为?

第 2 行到第 5 行:

k = i++ + j++;
k = i++ + ++j;
k = ++i + ++j;
k = ++i + j++;
Run Code Online (Sandbox Code Playgroud)

都是明确定义的。所有其他表达式都是未定义的,因为它们都试图通过在序列点之间多次评估表达式来修改对象的值(对于这些示例,序列点出现在终止每个语句的 ';' 处)。例如,i = i++;是未定义的,因为我们试图i通过赋值和后缀来修改值,++而没有中间序列点。FYI =运算符不引入序列点。|| && ?:,comma运算符引入序列点

对于定义的行为表达式,请您展示(而不是解释)编译器如何评估它们。

让我们开始

k = i++ + j++;
Run Code Online (Sandbox Code Playgroud)

表达式a++计算为 的当前a,并在下一个序列点之前的某个点a增加 1。因此,从逻辑上讲,计算类似于

k = 1 + 2; // i++ evaluates to 1, j++ evaluates to 2
i = i + 1; // i is incremented and becomes 2
j = j + 1; // j is incremented and becomes 3
Run Code Online (Sandbox Code Playgroud)

然而...

计算表达式i++和的确切顺序以及j++应用它们的副作用的顺序是未指定的。以下是完全合理的操作顺序(使用伪汇编代码):

mov j, r0        ; read the value of j into register r0
mov i, r1        ; read the value of i into register r1
add r0, r1, r2   ; add the contents of r0 to r1, store result to r2
mov r2, k        ; write result to k
inc r1           ; increment value of i
inc r0           ; increment value of j
mov r0, j        ; store result of j++
mov r1, i        ; store result of i++
Run Code Online (Sandbox Code Playgroud)

不要假设对算术表达式进行从左到右的评估。不要假设 的操作数++和在--评估后立即更新。

因此,表达式 like 的结果i++ + ++i会因编译器、编译器设置甚至周围的代码而异。行为未定义,因此编译器不需要“做正确的事情”,无论是什么。你会得到一个结果,但它不一定是你期望的结果,而且它不会在所有平台上保持一致。

看着

k = i++ + ++j;
Run Code Online (Sandbox Code Playgroud)

逻辑评估

k = 1 + 3  // i++ evaluates to i (1), ++j evaluates to j + 1 (2 + 1 = 3)
i = i + 1
j = j + 1
Run Code Online (Sandbox Code Playgroud)

同样,这是一种可能的操作顺序:

mov j, r0
inc r0
mov i, r1
add r0, r1, r2
mov r2, k
mov r0, j
inc r1
mov r1, i
Run Code Online (Sandbox Code Playgroud)

或者它可以做其他事情。编译器可以自由更改单个表达式的求值顺序,前提是它会导致更有效的操作顺序(我的示例几乎肯定不是这样)。