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)
题:
我想知道上图中显示的所有表达式(在4组中)是否有未定义的行为?如果只有一些具有未定义的行为,那些行为和哪些不行?
对于已定义的行为表达式,请显示(不解释) 编译器如何评估它们.只是为了确保,如果我正确地获得了这个预增量和后增量.
背景:
今天,我参加了校园面试,在那里我被要求解释i++ + ++i
给定值的结果i
.在gcc中编译该表达式后,我意识到我在采访中给出的答案是错误的.我决定将来不会犯这样的错误,因此,尝试编译前后增量运算符的所有可能组合,并在gcc中编译它们然后尝试解释结果.我挣扎了2个多小时.我找不到评估这些表达式的单一行为.所以,我放弃了,转向stackoverflow.经过一点点阅读档案,发现有类似sequence point
和未定义的行为.
除第一组外,其他三组中的所有表达式都具有未定义的行为.
如何评估定义的行为(第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组具有未定义的行为,因为=
运算符不引入序列点.
这些语句中没有序列点。它们之间有序列点。
如果您在连续序列点之间修改同一对象两次(在本例中,通过=
或通过 prefix 或 postfix ++
),则行为未定义。因此,第一组 4 条语句的行为已明确定义;其他人的行为是不确定的。
如果定义了行为,则i++
生成的先前值i
,并作为副作用i
通过添加1
到它来进行修改。 通过添加来++i
修改,然后产生修改后的值。i
1
我想知道上图中显示的所有表达式(分 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)
或者它可以做其他事情。编译器可以自由更改单个表达式的求值顺序,前提是它会导致更有效的操作顺序(我的示例几乎肯定不是这样)。