c - visual - when is rvo guaranteed




Is the conditional move optimization against the C standard? (2)

The C Standard describes an abstract machine executing C code. A compiler is free to perform any optimization as long as that abstraction is not violated, i.e. a conforming program cannot tell the difference.

It is a common optimization to use conditional move (assembly cmov) to optimize the conditional expression ?: in C. However, the C standard says:

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)

For example, the following C code

#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;
}

will generate optimized related asm code as follows:

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

According to the standard, the conditional expression will only have one branch evaluated. But here both branches are evaluated, which is against the standard's semantics. Is this optimization against the C standard? Or do many compiler optimizations have something inconsistent with the language standard?


The optimization is legal, due to the "as-if rule", i.e. C11 5.1.2.3p6.

A conforming implementation is just required to produce a program that when run produces the same observable behaviour as the execution of the program using the abstract semantics would have produced. The rest of the standard just describes these abstract semantics.

What the compiled program does internally does not matter at all, the only thing that matters is that when the program ends it does not have any other observable behaviour, except reading the a and b and printing the value of a + 1 or b + 2 depending on which one a or bis greater, unless something occurs that causes the behaviour be undefined. (Bad input causes a, b be uninitialized and therefore accesses undefined; range error and signed overflow can occur too.) If undefined behaviour occurs, then all bets are off.


Since accesses to volatile variables must be evaluated strictly according to the abstract semantics, you can get rid of the conditional move by using volatile here:

#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;
}

compiles to

        call    [email protected]
        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    [email protected]

        [...]

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

by my GCC Ubuntu 7.2.0-8ubuntu3.2





ternary-operator