查看此代码:
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);
}
哪个数组条目得到更新?0还是2?
C规范中是否有一部分表明了在这种特殊情况下操作的优先级?
查看此代码:
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);
}
哪个数组条目得到更新?0还是2?
C规范中是否有一部分表明了在这种特殊情况下操作的优先级?
要在arr[global_var] = update_three(2)
中执行赋值,C实现必须对操作数求值,并作为副作用,更新左操作数的存储值.C 2018 6.5.16(关于赋值)第3段告诉我们,左右操作数没有顺序:
操作数的求值是未排序的.
这意味着C实现可以自由地首先计算lvalue arr[global_var]
(通过"计算lvalue",我们的意思是找出这个表达式所指的是什么),然后计算update_three(2)
,最后将后者的值赋给前者;或者先计算update_three(2)
,然后计算lvalue,然后将前者赋给后者;或者以某种混合的方式计算lvalue和update_three(2)
,然后将右边的值赋给左边的lvalue.
在所有情况下,左值的赋值必须排在最后,因为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;
是full expression,return 3;
中的3
也是full expression,按照6.8 4:
是一个表达式,它既不是另一个表达式的一部分,也不是声明符或抽象声明符…的一部分
然后在这两个表达式之间有一个序列点,同样按照6.8 4:
…在一个完整表达式的求值和下一个要求值的完整表达式的求值之间有一个序列点.
因此,C实现可以首先判断arr[global_var]
,然后进行函数调用,在这种情况下,它们之间存在序列点,因为在函数调用之前有一个序列点,或者它可以在函数调用中判断global_var = val;
,然后判断arr[global_var]
,在这种情况下,它们之间存在序列点,因为在完整表达式之后有一个序列点.因此,行为是未指定的-这两件事中的任何一件都可以首先判断-但它并不是未定义的.