i == (i = 2) 的结果是什么?

44
运行以下代码:
// In Java, output #####
public static void main(String[] args) {
    int i = 1;

    if(i == (i = 2)) {
        System.out.println("@@@@@");
    } else {
        System.out.println("#####");
    }
}

但是:

// In C, output @@@@@,I did test on Clion(GCC 7.3) and Visual Studio 2017
int main(int argc, char *argv[]) {
    int i = 1;

    if(i == (i = 2)) {
        printf("@@@@@");
    } else {
        printf("#####");
    }

    return 0;
}

问这个问题的动机来自以下代码:

// The code is from the JDK 11 - java.util.concurrent.atomic.AtomicInteger
// I am curious about the behavior of the variable prev.
public final int getAndUpdate(IntUnaryOperator updateFunction) {
    int prev = get(), next = 0;
    for (boolean haveNext = false;;) {
        if (!haveNext)
            next = updateFunction.applyAsInt(prev);
        if (weakCompareAndSetVolatile(prev, next))
            return prev;
        haveNext = (prev == (prev = get()));
    }
}

那么,如何解释上述两种不同的执行模式呢?


12
首先指出两种执行模式是两种完全不同的语言,从而解释了它们的区别。它们恰好共享一些语法,但这就是相似之处的尽头。 - StoryTeller - Unslander Monica
4
结果是:一段混乱的代码。最好不要模仿这个,除非你正在参加Java混淆竞赛。 - Tristan
4
可能是 C 和 Java 中的逻辑差异 的重复问题。 - user202729
1
未定义行为和序列点 - phuclv
1
@AnttiHaapala 刚刚发现了“接受”功能... - kangjianwei
显示剩余7条评论
3个回答

60
执行表达式i == (i = 2)的C程序的行为是未定义的。它来自C11 6.5p22
如果标量对象上的副作用在顺序上不相对于同一标量对象上的不同副作用或使用同一标量对象的值计算而言是未排序的,则其行为是未定义的。如果一个表达式的子表达式有多个可允许的排序方式,并且在任何排序中出现了这样的未排序副作用,则其行为是未定义的。84)
左侧的i是对标量对象i的值进行值计算,右侧的i = 2具有将值2分配给i的副作用。 ==的LHS和RHS在顺序上不相对于彼此。因此,在C中整个程序毫无意义。
使用gcc -Wall编译,GCC会输出:
unsequenced.c:5:16: warning: operation on ‘i’ may be undefined [-Wsequence-point]
     if(i == (i = 2)) {
             ~~~^~~~

与C语言不同,Java保证操作数的求值顺序(从左到右),因此

haveNext = (prev == (prev = get()));

在Java中是正确的。在RHS上的副作用评估之前,LHS的值被严格确定。

在C语言中,你需要将其写成类似以下的形式:

newPrev = get();
haveNext = (prev == newPrev);
prev = newPrev;

为什么LHS中的“i”是值计算?这在标准中有指定吗? - Some Name
1
@SomeName 当然在标准中有指定。 :D - Antti Haapala -- Слава Україні
1
@SomeName 5.1.2.3p2中提到“左值表达式的值计算”,这里需要考虑一种情况,即i是一个左值,它的值需要被计算,因为它不是一个操作数。6.3.2.1p2中说“转换为指定对象存储的值”,但这正是由5.1.2.3p2所提到的。 - Antti Haapala -- Слава Україні
非常有用。谢谢。 - Some Name
@imallett 错了,在C语言中,赋值操作的值是一个表达式,绝对不是一个左值。 - Antti Haapala -- Слава Україні
显示剩余7条评论

16

根据Java语言规范(§15.7),Java编程语言保证操作符的操作数按照特定的求值顺序进行求值,即从左到右。

规范(§15.21.1) 还指出:

==运算符所产生的值,如果左操作数的值等于右操作数的值,则结果为true;否则结果为false

因此,在Java中,if语句在运行时将会像下面这样,并显然得出false的结果:

if (1 == 2) {

}
在C语言中,这是未定义的(请参见Antti's answer)。

5
在C语言中,i == (i = 2)的行为是未定义的,因为它试图在没有中间序列点的情况下既更新对象又在计算中使用该对象的值。结果将根据编译器、编译器设置甚至周围代码的不同而有所不同。

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