为什么 sum1 = 46 和 sum2 = 48
Why sum1 = 46 and sum2 = 48
在下面提到的代码中,当 sum2 中 right.But 的运算符优先级为 48 时,sum1 变量的答案为 46,并且它的优先级为从右到左。为什么这些答案越来越不同。
#include <stdio.h>
int func(int *k){
*k+=4;
return 3 * (*k)-1;
}
void main() {
int i = 10, j = 10, sum1, sum2;
sum1 = (i / 2) + func(&i);
sum2 = func(&j)+(j/2);
printf("%d\n",sum1);
printf("%d",sum2);
}
程序有未定义的行为,因为加法运算符中操作数的求值顺序未指定,并且此类操作数求值未排序。
注意在赋值表达式中,变量i
和j
正在被改变并且这些改变没有顺序。 i / 2
或 j / 2
可以在函数调用之前计算,反之亦然。
变量名前面的 &(&i 或 &j)发送一个指针,表示保存对变量的任何更改。在这种情况下,func 调用获取一个变量并将其加 4,然后 returns 一些基于该变量的值。该函数仍然更改了变量的值,并且由于方程是从左到右处理的,因此每次后续使用该变量都会反映出该变化。
sum1 = (i/2) +func(&i) (where i = 10)
--> sum1 = 5 + func(&i)
--> sum1 = 5 + 41 (and now i = 14)
sum2 = func(&j) + (j/2) (where j = 10)
--> sum2 = 41 + (j/2) (and now j = 14)
--> sum2 = 41 + 7
在表达式 (i / 2) + func(&i)
中,编译器(或通常的 C 实现)可以自由地先计算 i / 2
或先计算 func(&i)
。同样,在 func(&j) + (j/2)
中,编译器可以自由地首先评估 func(&j)
或 j/2
。
优先级无关紧要。优先级告诉我们一个表达式是如何构造的,但它并不能完全确定它被评估的顺序。优先级告诉我们,在a * b + c * d
中,结构必须是(a * b) + (c * d)
。在 a + b + c
中,优先级以 +
从左到右的关联形式告诉我们结构必须是 (a + b) + c
。它并没有说明 a
必须在 c
之前计算。例如,在 a() + b() + c()
中,结构是 (a() + b()) + c()
,但编译器可以按任何顺序调用函数,如果需要,将它们的结果保存在临时寄存器中,然后将结果相加。
在 func(&j)+(j/2)
中,没有从右到左的优先顺序或关联。 C 标准中没有规则说必须在 func(&j)
.
之前计算 j/2
在没有其他约束的情况下,编译器可能倾向于从左到右计算子表达式。但是,各种因素可能会改变这一点。例如,如果一个子表达式出现多次,编译器可能会提前对其求值并保留其值以供重用。本质上,编译器构建一个树结构来描述它需要评估的表达式,然后寻找最佳方法来评估它们。它不一定从左到右进行,您不能依赖任何特定的评估顺序。
关于测序的问题
C 标准在 C 2018 6.5 2 中有一条规则,即如果对对象的修改(如语句 *k+=4;
中对 i
的修改)相对于值是无序的使用同一对象进行计算,如 i / 2
中的 i
发生,则行为未定义。但是,此代码中不会出现此问题,因为修改和值计算是不确定排序的,而不是未排序的:6.5.2.2 10 说“......调用函数(包括其他函数调用)中的每个评估之前没有特别排序或者在被调用函数的主体执行之后相对于被调用函数的执行不确定地排序。” C 5.1.2.3 3 说“......当 A 排序在 B 之前或之后时,评估 A 和 B 是 不确定排序的 ,但未指定哪个......”
在下面提到的代码中,当 sum2 中 right.But 的运算符优先级为 48 时,sum1 变量的答案为 46,并且它的优先级为从右到左。为什么这些答案越来越不同。
#include <stdio.h>
int func(int *k){
*k+=4;
return 3 * (*k)-1;
}
void main() {
int i = 10, j = 10, sum1, sum2;
sum1 = (i / 2) + func(&i);
sum2 = func(&j)+(j/2);
printf("%d\n",sum1);
printf("%d",sum2);
}
程序有未定义的行为,因为加法运算符中操作数的求值顺序未指定,并且此类操作数求值未排序。
注意在赋值表达式中,变量i
和j
正在被改变并且这些改变没有顺序。 i / 2
或 j / 2
可以在函数调用之前计算,反之亦然。
变量名前面的 &(&i 或 &j)发送一个指针,表示保存对变量的任何更改。在这种情况下,func 调用获取一个变量并将其加 4,然后 returns 一些基于该变量的值。该函数仍然更改了变量的值,并且由于方程是从左到右处理的,因此每次后续使用该变量都会反映出该变化。
sum1 = (i/2) +func(&i) (where i = 10)
--> sum1 = 5 + func(&i)
--> sum1 = 5 + 41 (and now i = 14)
sum2 = func(&j) + (j/2) (where j = 10)
--> sum2 = 41 + (j/2) (and now j = 14)
--> sum2 = 41 + 7
在表达式 (i / 2) + func(&i)
中,编译器(或通常的 C 实现)可以自由地先计算 i / 2
或先计算 func(&i)
。同样,在 func(&j) + (j/2)
中,编译器可以自由地首先评估 func(&j)
或 j/2
。
优先级无关紧要。优先级告诉我们一个表达式是如何构造的,但它并不能完全确定它被评估的顺序。优先级告诉我们,在a * b + c * d
中,结构必须是(a * b) + (c * d)
。在 a + b + c
中,优先级以 +
从左到右的关联形式告诉我们结构必须是 (a + b) + c
。它并没有说明 a
必须在 c
之前计算。例如,在 a() + b() + c()
中,结构是 (a() + b()) + c()
,但编译器可以按任何顺序调用函数,如果需要,将它们的结果保存在临时寄存器中,然后将结果相加。
在 func(&j)+(j/2)
中,没有从右到左的优先顺序或关联。 C 标准中没有规则说必须在 func(&j)
.
j/2
在没有其他约束的情况下,编译器可能倾向于从左到右计算子表达式。但是,各种因素可能会改变这一点。例如,如果一个子表达式出现多次,编译器可能会提前对其求值并保留其值以供重用。本质上,编译器构建一个树结构来描述它需要评估的表达式,然后寻找最佳方法来评估它们。它不一定从左到右进行,您不能依赖任何特定的评估顺序。
关于测序的问题
C 标准在 C 2018 6.5 2 中有一条规则,即如果对对象的修改(如语句 *k+=4;
中对 i
的修改)相对于值是无序的使用同一对象进行计算,如 i / 2
中的 i
发生,则行为未定义。但是,此代码中不会出现此问题,因为修改和值计算是不确定排序的,而不是未排序的:6.5.2.2 10 说“......调用函数(包括其他函数调用)中的每个评估之前没有特别排序或者在被调用函数的主体执行之后相对于被调用函数的执行不确定地排序。” C 5.1.2.3 3 说“......当 A 排序在 B 之前或之后时,评估 A 和 B 是 不确定排序的 ,但未指定哪个......”