条件移动优化是否针对 C 标准?

Is the conditional move optimization against the C standard?

在C中使用条件移动(汇编cmov)来优化条件表达式?:是一种常见的优化。但是,C标准说:

The first operand is evaluated; there is a sequence point between its evaluation and the evaluation of the second or third operand (whichever is evaluated). The second operand is evaluated only if the first compares unequal to 0; the third operand is evaluated only if the first compares equal to 0; the result is the value of the second or third operand (whichever is evaluated), converted to the type described below.110)

例如下面的C代码

#include <stdio.h>

int main() {
    int a, b;
    scanf("%d %d", &a, &b);
    int c= a > b ? a + 1 : 2 + b;
    printf("%d", c);
    return 0;
}

会生成优化后的相关asm代码如下:

call    __isoc99_scanf
movl    (%rsp), %esi
movl    4(%rsp), %ecx
movl    , %edi
leal    2(%rcx), %eax
leal    1(%rsi), %edx
cmpl    %ecx, %esi
movl    $.LC1, %esi
cmovle  %eax, %edx
xorl    %eax, %eax
call    __printf_chk

根据标准,条件表达式只会计算一个分支。但是这里对两个分支都进行了评估,这违反了标准的语义。这是针对 C 标准的优化吗?还是很多编译器优化有不符合语言标准的地方?

C 标准描述了一个执行 C 代码的抽象机器。只要不违反抽象,编译器就可以自由执行任何优化,即符合标准的程序无法区分。

由于"as-if rule", i.e. C11 5.1.2.3p6,优化是合法的。

只需要一个符合规范的实现来生成一个程序,当 运行 生成与使用抽象语义 [=32=32=] 程序执行时相同的 可观察行为=] 会产生 。标准的其余部分只是描述了这些抽象语义

编译后的程序在内部做什么根本不重要,唯一重要的是当程序结束时它没有任何其他可观察到的行为,除了读取 ab 并打印 a + 1b + 2 的值,具体取决于哪个 ab 更大,除非发生某些导致行为未定义的事情。 (错误的输入导致 a、b 未初始化,因此访问未定义;范围错误和有符号溢出也可能发生。)如果发生未定义的行为,则所有赌注都关闭。


由于必须根据抽象语义严格评估对 volatile 变量的访问,因此您可以在此处使用 volatile 来摆脱条件移动:

#include <stdio.h>

int main() {
    volatile int a, b;
    scanf("%d %d", &a, &b);
    int c = a > b ? a + 1 : 2 + b;
    printf("%d", c);
    return 0;
}

编译为

        call    __isoc99_scanf@PLT
        movl    (%rsp), %edx
        movl    4(%rsp), %eax
        cmpl    %eax, %edx
        jg      .L7
        movl    4(%rsp), %edx
        addl    , %edx
.L3:
        leaq    .LC1(%rip), %rsi
        xorl    %eax, %eax
        movl    , %edi
        call    __printf_chk@PLT

        [...]

.L7:
        .cfi_restore_state
        movl    (%rsp), %edx
        addl    , %edx
        jmp     .L3

通过我的 GCC Ubuntu 7.2.0-8ubuntu3.2