web-dev-qa-db-fra.com

L'optimisation du déplacement conditionnel est-elle conforme à la norme C?

C'est une optimisation courante que d'utiliser le déplacement conditionnel (Assembly cmov) pour optimiser l'expression conditionnelle ?: en C. Cependant, la norme C dit:

Le premier opérande est évalué; il existe un point de séquence entre son évaluation et l'évaluation du deuxième ou du troisième opérande (celui qui est évalué). Le deuxième opérande n'est évalué que si le premier est différent de 0; le troisième opérande n'est évalué que si le premier est égal à 0; le résultat est la valeur du deuxième ou du troisième opérande (celui qui est évalué), converti dans le type décrit ci-dessous. 110)

Par exemple, le code C suivant

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

générera le code asm associé optimisé comme suit:

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

Selon la norme, l'expression conditionnelle n'aura qu'une seule branche évaluée. Mais ici, les deux branches sont évaluées, ce qui est contraire à la sémantique de la norme. Cette optimisation est-elle contre la norme C? Ou bien de nombreuses optimisations du compilateur ont-elles quelque chose d'incompatible avec la norme de langage?

25
manifold

L'optimisation est légale, en raison de la "règle comme si" , c'est-à-dire C11 5.1.2.3p6 .

Une implémentation conforme est simplement requise pour produire un programme qui, lors de son exécution, produit le même comportement observable que l'exécution du programme en utilisant la sémantique abstraite aurait produit . Le reste de la norme décrit simplement ces sémantiques abstraites .

Ce que le programme compilé fait en interne n'a pas d'importance du tout, la seule chose qui compte, c'est que lorsque le programme se termine, il n'a aucun autre comportement observable, sauf la lecture de a et b et l'impression la valeur de a + 1 ou b + 2 selon lequel a ou b est supérieur, à moins que quelque chose ne se produise et que le comportement ne soit défini. (Une entrée incorrecte entraîne la non initialisation de a, b et donc l'accès non défini; une erreur de plage et un débordement signé peuvent également se produire.) Si un comportement non défini se produit, tous les paris sont désactivés.


Puisque les accès aux variables volatiles doivent être évalués strictement selon la sémantique abstraite, vous pouvez vous débarrasser du déplacement conditionnel en utilisant volatile ici:

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

compile en

        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

par mon GCC Ubuntu 7.2.0-8ubuntu3.2

42
Antti Haapala

La norme C décrit une machine abstraite exécutant du code C. Un compilateur est libre d'effectuer n'importe quelle optimisation tant que cette abstraction n'est pas violée, c'est-à-dire qu'un programme conforme ne peut pas faire la différence.

26
Jens