C语言的求值顺序

3
  1. x+=x*=x 是否属于未定义行为?
  2. 有人可以解释一下顺序求值中的这条规则吗?什么是“单次求值”?什么是“单次求值”的相反操作?

    14) 关于不确定顺序的函数调用,复合赋值运算符以及前缀和后缀递增和递减运算符的操作都是单次求值。


1
“x+=x*=x” 是未定义行为吗?是的,因为多个赋值之间没有序列点(即值更改)。 - Sourav Ghosh
6
请勿在一个C语言问题中提供C++参考资料,它们是不同的编程语言。 - Andrew Henle
2
@AndrewHenle 一如既往:“在前一个和下一个序列点之间,一个对象通过表达式的求值最多只能被修改一次其存储的值。此外,先前的值只能被读取以确定要存储的值。” - Antti Haapala -- Слава Україні
1
很明显这是未定义的。x在序列点之间被修改了两次。为什么还会有任何疑问或争论呢? - Steve Summit
1
这真的需要一个语言律师标签。无论它是否未定义行为,它都是一种暴行,除了为了语言律师之外,永远不应该被编写! - William Pursell
显示剩余9条评论
2个回答

3

x+=x*=x的行为是未定义的,因为在序列点之间x被赋值了两次。


C11、C17中对应14)的文本如下:

形式为E1 op= E2的复合赋值等同于简单的赋值表达式E1 = E1 op (E2),除了lvalue E1仅被计算一次,并且在不确定顺序的函数调用方面,复合赋值的操作是单一评估。

我认为它的意思是

int x = 0;

int foo(void) {
    x = 5;
    return x;
}

int main(void) {
    int y = foo() + (x += 2); 
}

将具有其中之一的行为。
int main(void) {
    int _tmp = x += 2;
    int y = foo() + _tmp; 
}

或者

int main(void) {
    int _tmp = foo();
    int y = _tmp + (x += 2); 
}

并且不能被分割,例如:

int main(void) {
    int _tmp = x;
    int _tmp2 = foo();
    x = _tmp + 2;
    int y = _tmp2 + x; 
}

请注意,这个保证是C11中新增的功能,在C89和C99中并不存在。

如果给定类似 *p += f(); 的东西,标准是否明确指出调用是否可以在评估 p 和对该左值的访问之间发生?我认为C和C++标准都将受益于承认作为赋值运算符左操作数使用的左值,或者其地址被取出,必须在分配或第一次使用生成的地址之前“解析”,并明确指定这种解析如何与表达式评估的其他部分排序。 - supercat
那么int _tmp = x+2; int _tmp2 = foo(); x = _tmp;呢?在这种情况下,x也变成了2。x+=2的值是按顺序计算的,但它的副作用不是。在C11之后会发生这种情况吗? - imba-tjd
@supercat 好奇我现在才看到这个。看起来标准在脚注中建议可以在之前评估p以获取指针值,只有解引用会在“单个操作”中发生http://port70.net/~nsz/c/c11/n1570.html#note113 - Antti Haapala -- Слава Україні
@AnttiHaapala--СлаваУкраїні:我认为标准的这部分仅适用于使用原子类型时。 - supercat

1

1)是的。

C17 6.5.16 赋值运算符,§3

操作数的评估是无序的。

这使得它由于 C17 6.5 §2 而是未定义的行为

如果对标量对象的副作用相对于同一标量对象上的另一个不同的副作用或使用相同标量对象的值计算是未排序的,则行为未定义。

每个赋值都是一个副作用。C99有相同的规则,但更容易理解:

C99 6.5 §2

在前一个和下一个序列点之间,对象的存储值最多可以通过表达式的求值修改一次。此外,应仅读取先前的值以确定要存储的值。


2) 所有带有C11序列更改的文本阅读起来相当混乱。它们使用一些在任何地方都没有正式定义的术语,例如“单个评估”。

规范文本为C17 6.5.15.2 §3

形式为E1 op = E2的复合赋值等同于简单的赋值表达式E1 = E1 op(E2),除了lvalue E1仅被计算一次,并且在关于不确定顺序的函数调用的情况下,复合赋值的操作是单个评估。

我想这只是意味着像int x;x += 1 这样的操作应该产生如下的机器代码:

  • 将1存储到寄存器中
  • 将x添加到寄存器
  • 将寄存器写入x

否则,假设在这些操作之间发生了一些序列,更新了x。但无论如何,复合赋值都不是原子的,所以我不太理解标准在这里指的是什么。


我猜它的意思是 z = foo() + (x += y)foo 要么看到 x 已经被改变,在这种情况下,通过改变全局 xfoo() 不能影响评估 x += y 的值,要么 foo() 在完全执行之前,x += y 就已经被评估了。 - Antti Haapala -- Слава Україні
我认为在这种情况下,复合赋值是原子性的,不能与其他函数交错。 - Patrick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接