Является ли условная оптимизация движения против стандарта C?

Обычная оптимизация использования условного перемещения (сборка 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;
}

будет генерировать оптимизированный связанный код 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

В соответствии со стандартом условное выражение будет иметь только одну ветвь. Но здесь оцениваются обе ветки, что противоречит стандартной семантике. Эта оптимизация против стандарта C? Или у многих оптимизаторов компилятора есть что-то несовместимое с языковым стандартом?

Ответ 1

Оптимизация является законной из-за правила "как есть ", то есть C11 5.1.2.3p6.

Соответствующая реализация требуется только для создания программы, которая при запуске производит такое же наблюдаемое поведение, как и выполнение программы с использованием абстрактной семантики. Остальная часть стандарта описывает только эти абстрактные семантики.

То, что скомпилированная программа делает внутренне, не имеет никакого значения, единственное, что имеет значение, - это то, что когда программа заканчивается, у нее нет другого наблюдаемого поведения, кроме чтения a и b и печати значения a + 1 или b + 2 в зависимости от того, какой a или b больше, если только что-то не происходит, что приводит к неопределенному поведению. (Плохой ввод приводит к тому, что a, b не инициализируется и, следовательно, обращается к undefined, ошибка диапазона и переполнение подписей также могут возникать.) Если происходит неопределенное поведение, тогда все ставки отключены.


Поскольку доступ к изменчивым переменным должен оцениваться строго в соответствии с абстрактной семантикой, вы можете избавиться от условного перемещения, используя 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    [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

моим GCC Ubuntu 7.2.0-8ubuntu3.2

Ответ 2

Стандарт C описывает абстрактную машину, выполняющую код C. Компилятор может свободно выполнять любую оптимизацию, если эта абстракция не нарушена, то есть соответствующая программа не может отличить эту разницу.