针对C标准的条件移动优化是什么?

man*_*old 25 c assembly ternary-operator compiler-optimization language-lawyer

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

第一个操作数被评估; 在其评估与第二或第三操作数的评估之间存在一个序列点(以评估者为准).仅当第一个操作数不等于0时才评估第二个操作数; 仅当第一个操作数比较等于0时才评估第三个操作数; 结果是第二个或第三个操作数的值(无论哪个被评估),转换为下面描述的类型.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;
}
Run Code Online (Sandbox Code Playgroud)

将生成优化的相关asm代码,如下所示:

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

根据标准,条件表达式将仅评估一个分支.但是这里对两个分支进行了评估,这违反了标准的语义.这是针对C标准的优化吗?或者许多编译器优化是否与语言标准不一致?

Ant*_*ala 42

由于"as-if规则",即C11 5.1.2.3p6,优化是合法的.

只需要一个符合要求的实现来生成一个程序,该程序在运行时产生使用抽象语义产生的程序执行相同的可观察行为.标准的其余部分只描述了这些抽象语义.

什么是编译程序不会在内部完全不打紧,唯一重要的事情是,在程序结束时不会有任何其他观察到的行为,除了阅读ab与印刷的值a + 1或者b + 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;
}
Run Code Online (Sandbox Code Playgroud)

编译成

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

        [...]

.L7:
        .cfi_restore_state
        movl    (%rsp), %edx
        addl    $1, %edx
        jmp     .L3
Run Code Online (Sandbox Code Playgroud)

由我的GCC Ubuntu 7.2.0-8ubuntu3.2

  • @ArturBiesiadowski:好点:C标准中observable的定义不包括性能,并且严格限制在符合C程序的情况下*可以*可移动*观察的内容(没有UB,如`(char*)my_func`) ,因此通过使用可预测/不可预测的数据进行分析来排除检查branchy/branchless([为什么处理排序数组比未排序数组更快?](/sf/ask/785946661/)).这就是为什么使用调试模式去优化和完全优化(保持寄存器中的内容,甚至自动向量化)进行编译都是合法的. (3认同)

Jen*_*ens 26

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

  • 特别是,只要第2 /第3个操作数没有任何可见的副作用,编译器就可以在不以任何方式违反标准的情况下对它们进行评估. (14认同)
  • @manifold:是的,但从抽象机器的角度来看,只评估了一个. (2认同)