查看此代码:

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_vararr[global_var]中使用并且在函数调用update_three(2)中更新.

6.5.2.2 10告诉我们在调用函数之前有一个序列点:

在函数指示符和实际参数的求值之后,但在实际调用之前,有一个序列点…

在函数内部,global_var = val;full expressionreturn 3;中的3也是full expression,按照6.8 4:

是一个表达式,它既不是另一个表达式的一部分,也不是声明符或抽象声明符…的一部分

然后在这两个表达式之间有一个序列点,同样按照6.8 4:

…在一个完整表达式的求值和下一个要求值的完整表达式的求值之间有一个序列点.

因此,C实现可以首先判断arr[global_var],然后进行函数调用,在这种情况下,它们之间存在序列点,因为在函数调用之前有一个序列点,或者它可以在函数调用中判断global_var = val;,然后判断arr[global_var],在这种情况下,它们之间存在序列点,因为在完整表达式之后有一个序列点.因此,行为是未指定的-这两件事中的任何一件都可以首先判断-但它并不是未定义的.

C++相关问答推荐

有效地计算由一组点构成的等边三角形和等腰三角形的数量

为什么复合文字(C99)的返回会生成更多的汇编代码?

有没有更简单的方法从用户那里获取数据类型来计算结果

#If指令中未定义宏?

可以将C变量限制为特定的读/写速度吗?

如何在不使用其他数组或字符串的情况下交换字符串中的两个单词?

VS代码';S C/C++扩展称C23真关键字和假关键字未定义

在C中访问数组中的特定值

在C++中父进程和子进程中的TAILQ队列同步问题

处理EPOLL_WAIT中的接收数据和连接关闭信号

在C中创建任意类型的只读指针参数

在函数外部使用内联ASM时无法指定操作数

安全倒计时循环

在C程序中使用Beaglebone Black UART的问题

如何在不更改格式说明符的情况下同时支持双精度和长双精度?

C中的char**v*char[]

在文件描述符上设置FD_CLOEXEC与将其传递给POSIX_SPOWN_FILE_ACTIONS_ADCLOSE有区别吗?

在C中使用字符串时是否不需要内存分配?

将char*铸造为空**

为什么创建局部变量的指针需要过程在堆栈上分配空间?