Jim*_*ion 47 c language-lawyer order-of-execution
看看这段代码:
static int global_var = 0;
int update_three(int val)
{
global_var = val;
return 3;
}
int main()
{
int arr[5];
arr[global_var] = update_three(2);
}
Run Code Online (Sandbox Code Playgroud)
哪个数组条目得到更新?0 还是 2?
C 的规范中是否有部分指示在这种特殊情况下操作的优先级?
Eri*_*hil 52
要在 中执行赋值arr[global_var] = update_three(2),C 实现必须评估操作数,并且作为副作用,更新左操作数的存储值。C 2018 6.5.16(关于赋值)第 3 段告诉我们左右操作数没有排序:
操作数的评估是无序的。
这意味着 C 实现可以自由地首先计算左值 arr[global_var](通过“计算左值”,我们的意思是弄清楚这个表达式所指的是什么),然后计算update_three(2),最后将后者的值分配给前者;或者先求值update_three(2),然后计算左值,然后将前者赋给后者;或以update_three(2)某种混合方式评估左值,然后将右值分配给左左值。
在所有情况下,将值分配给左值必须放在最后,因为 6.5.16 3 还说:
… 更新左操作数的存储值的副作用在左右操作数的值计算之后排序…
由于global_var违反 6.5 2使用和单独更新它,有些人可能会考虑未定义的行为,它说:
如果对标量对象的副作用相对于对同一标量对象的不同副作用或使用同一标量对象的值进行的值计算而言是未排序的,则行为是未定义的……
许多 C 语言从业者都非常熟悉,诸如此类的表达式的行为x + x++并未由 C 标准定义,因为它们都使用 的值x并在同一表达式中单独修改它而没有排序。然而,在这种情况下,我们有一个函数调用,它提供了一些排序。在函数调用中global_var使用arr[global_var]和更新update_three(2)。
6.5.2.2 10 告诉我们在函数调用之前有一个序列点:
在函数指示符和实际参数的计算之后但在实际调用之前有一个序列点......
在函数内部,global_var = val;是一个完整的表达式,3in也是如此return 3;,每 6.8 4:
甲充分表达是不是另一个表达式的一部分,也不是说明符或抽象声明符的一部分的表达...
然后在这两个表达式之间有一个序列点,再次按照 6.8 4:
… 在对完整表达式的求值和对下一个要求值的完整表达式的求值之间有一个序列点。
因此,C 实现可能arr[global_var]先求值,然后执行函数调用,在这种情况下它们之间有一个序列点,因为在函数调用之前有一个序列点,或者它可能global_var = val;在函数调用中求值然后arr[global_var],在这种情况下有它们之间有一个序列点,因为在完整表达式之后有一个。所以行为是未指定的——这两个东西中的任何一个都可能首先被评估——但它不是未定义的。
dbu*_*ush 26
这里的结果是不确定的。
虽然决定子表达式如何分组的表达式中的操作顺序已明确定义,但未指定求值顺序。在这种情况下,这意味着要么global_var可以先读取,要么可以先调用update_three,但无法知道哪个。
这里没有未定义的行为,因为函数调用引入了一个序列点,函数中的每个语句(包括修改global_var.
为了澄清起见,C 标准将第 3.4.3 节中的未定义行为定义为:
未定义的行为
在使用不可移植的或错误的程序结构或错误数据时的行为,本国际标准对此不作任何要求
并将第 3.4.4 节中未指定的行为定义为:
未指明的行为
使用未指定的值,或本国际标准提供两种或多种可能性的其他行为,并且在任何情况下都没有对选择的进一步要求
标准规定函数参数的计算顺序是未指定的,在这种情况下,这意味着要么arr[0]设置为 3 ,要么arr[2]设置为 3。