27 votes

a obtenu une réponse inattendue de l'expression x? y: z

Voici un extrait de code C ++ simple:

 int x1 = 10, x2=20, y1=132, y2=12, minx, miny, maxx, maxy;
x1<=x2 ? minx=x1,maxx=x2 : minx=x2,maxx=x1;
y1<=y2 ? miny=y1,maxy=y2 : miny=y2,maxy=y1;
cout<<"minx="<<minx<<"\n";
cout<<"maxx="<<maxx<<"\n";
cout<<"miny="<<miny<<"\n";
cout<<"maxy="<<maxy<<"\n";
 

Je pensais que le résultat devrait être:

 minx=10
maxx=20
miny=12
maxy=132
 

Mais en réalité, le résultat est:

 minx=10
maxx=10
miny=12
maxy=132
 

Quelqu'un pourrait-il expliquer pourquoi maxx n'a pas 20 ans? Merci.

40voto

perreal Points 47912

En raison de la priorité de l'opérateur, l'expression est analysée comme ceci:

 (x1<=x2 ? minx=x1,maxx=x2 : minx=x2), maxx=x1;
 

vous pouvez résoudre ce problème avec:

 (x1<=x2) ? (minx=x1,maxx=x2) : (minx=x2, maxx=x1);
 

Et en fait, vous n'avez pas besoin des deux premières paires de parenthèses. Vérifiez également cette question .

25voto

Daniel Fischer Points 114146

La priorité de l'opérateur conditionnel est plus grande que celle de l'opérateur virgule, donc

x1<=x2 ? minx=x1,maxx=x2 : minx=x2,maxx=x1;

est parenthesised comme

(x1<=x2 ? minx=x1,maxx=x2 : minx=x2),maxx=x1;

Ainsi, la dernière attribution est effectuée indépendamment de la condition.

Pour résoudre ce problème, vous pouvez

  • utiliser des parenthèses:

    x1 <= x2 ? (minx = x1, maxx = x2) : (minx = x2, maxx = x1);
    

    (vous n'avez pas besoin de les parenthèses dans l' true de la branche, mais c'est de l'OMI mieux d'avoir eux aussi).

  • l'utilisation de deux conditions:

    minx = x1 <= x2 ? x1 : x2;
    maxx = x1 <= x2 ? x2 : x1;
    
  • utiliser un if:

    if (x1 <= x2) {
        minx = x1;
        maxx = x2;
    } else {
        minx = x2;
        maxx = x1;
    }
    

Compilé avec ou sans optimisations, l' if de la version et de la parenthesised unique conditionnelle avec des virgules produire la même assemblée à la fois sous gcc (4.7.2) et clang (3.2), il est raisonnable de s'attendre à ce que d'autres compilateurs trop. La version avec les deux conditions produit différents assemblée, mais avec des optimisations, ces deux compilateurs n'émettent qu'un cmp instructions pour que, trop.

De mon point de vue, l' if version est la méthode la plus simple pour vérifier l'exactitude de l', donc préférable.

5voto

Mats Petersson Points 70074

Tandis que d'autres ont expliqué qu'en soit la cause du problème est, je pense que la "meilleure" solution devrait être d'écrire le conditionnel avec si:

int x1 = 10, x2=20, y1=132, y2=12, minx, miny, maxx, maxy;
if (x1<=x2) 
{ 
   minx=x1;
   maxx=x2;
}
else
{
   minx=x2; 
   maxx=x1;
}
if (y1<=y2)
{
    miny=y1;
    maxy=y2;
} 
else 
{
    miny=y2;
    maxy=y1;
}

Oui, il est plusieurs lignes en plus, mais c'est aussi plus facile à lire et clair exactement ce qui se passe (et si vous avez besoin à l'étape à travers elle dans le débogueur, vous pouvez facilement voir de quel côté il va).

Moderne compilateur doit être en mesure de convertir l'une de ces vers assez efficace conditionnelle affectations qui fait un bon travail en évitant les branches (et donc la "mauvaise direction de la prévision").

J'ai préparé un petit test, j'ai compilé à l'aide de

g++ -O2 -fno-inline -S -Wall ifs.cpp

Voici le code source (j'ai dû le faire paramètres pour s'assurer que le compilateur n'a pas simplement calculer la valeur correcte directement et simplement n' mov $12,%rdx, mais le fait de les comparer et de décider avec qui est plus grand):

void mine(int x1, int x2, int y1, int y2)
{
    int minx, miny, maxx, maxy;
    if (x1<=x2) 
    { 
    minx=x1;
    maxx=x2;
    }
    else
    {
    minx=x2; 
    maxx=x1;
    }
    if (y1<=y2)
    {
    miny=y1;
    maxy=y2;
    } 
    else 
    {
    miny=y2;
    maxy=y1;
    }

    cout<<"minx="<<minx<<"\n";
    cout<<"maxx="<<maxx<<"\n";
    cout<<"miny="<<miny<<"\n";
    cout<<"maxy="<<maxy<<"\n";
}

void original(int x1, int x2, int y1, int y2)
{
    int minx, miny, maxx, maxy;
    x1<=x2 ? (minx=x1,maxx=x2) : (minx=x2,maxx=x1);
    y1<=y2 ? (miny=y1,maxy=y2) : (miny=y2,maxy=y1);
    cout<<"minx="<<minx<<"\n";
    cout<<"maxx="<<maxx<<"\n";
    cout<<"miny="<<miny<<"\n";
    cout<<"maxy="<<maxy<<"\n";
}

void romano(int x1, int x2, int y1, int y2)
{
    int  minx, miny, maxx, maxy;

    minx = ((x1 <= x2) ? x1 : x2);
    maxx = ((x1 <= x2) ? x2 : x1);
    miny = ((y1 <= y2) ? y1 : y2);
    maxy = ((y1 <= y2) ? y2 : y1);
    cout<<"minx="<<minx<<"\n";
    cout<<"maxx="<<maxx<<"\n";
    cout<<"miny="<<miny<<"\n";
    cout<<"maxy="<<maxy<<"\n";
}

int main()
{
    int x1=10, x2=20, y1=132, y2=12;
    mine(x1, x2, y1, y2);
    original(x1, x2, y1, y2);
    romano(x1, x2, y1, y2);
    return 0;
}

Le code généré ressemble à ceci:

_Z4mineiiii:
.LFB966:
    .cfi_startproc
    movq    %rbx, -32(%rsp)
    movq    %rbp, -24(%rsp)
    movl    %ecx, %ebx
    movq    %r12, -16(%rsp)
    movq    %r13, -8(%rsp)
    movl    %esi, %r12d
    subq    $40, %rsp
    movl    %edi, %r13d
    cmpl    %esi, %edi
    movl    %edx, %ebp
    cmovg   %edi, %r12d
    cmovg   %esi, %r13d
    movl    $_ZSt4cout, %edi
    cmpl    %ecx, %edx
    movl    $.LC0, %esi
    cmovg   %edx, %ebx
    cmovg   %ecx, %ebp
        .... removed actual printout code that is quite long and unwieldy... 
_Z8originaliiii:
    movq    %rbx, -32(%rsp)
    movq    %rbp, -24(%rsp)
    movl    %ecx, %ebx
    movq    %r12, -16(%rsp)
    movq    %r13, -8(%rsp)
    movl    %esi, %r12d
    subq    $40, %rsp
    movl    %edi, %r13d
    cmpl    %esi, %edi
    movl    %edx, %ebp
    cmovg   %edi, %r12d
    cmovg   %esi, %r13d
movl    $_ZSt4cout, %edi
cmpl    %ecx, %edx
movl    $.LC0, %esi
cmovg   %edx, %ebx
cmovg   %ecx, %ebp
        ... print code goes here ... 
_Z6romanoiiii:
    movq    %rbx, -32(%rsp)
    movq    %rbp, -24(%rsp)
    movl    %edx, %ebx
    movq    %r12, -16(%rsp)
    movq    %r13, -8(%rsp)
    movl    %edi, %r12d
    subq    $40, %rsp
    movl    %esi, %r13d
    cmpl    %esi, %edi
    movl    %ecx, %ebp
    cmovle  %edi, %r13d
    cmovle  %esi, %r12d
movl    $_ZSt4cout, %edi
cmpl    %ecx, %edx
movl    $.LC0, %esi
cmovle  %edx, %ebp
cmovle  %ecx, %ebx
        ... printout code here.... 

Comme vous pouvez le voir, mine et original est identique, et romano utilise légèrement différents registres et une forme différente de l' cmov, mais sinon, ils font la même chose dans le même nombre d'instructions.

4voto

Roman Nikitchenko Points 3085

Question intéressante à la fois sur les opérations de la primauté et de la génération de code.

OK, , opération a la priorité la plus faible (plus faible, consultez le tableau de référence). Pour cette raison, votre code est le même que les lignes suivantes:

((x1<=x2) ? minx=x1,maxx=x2 : minx=x2),maxx=x1;
((y1<=y2) ? miny=y1,maxy=y2 : miny=y2),maxy=y1;

En fait, seul le C/C++ grammaire empêche premier , depuis le même comportement.

Encore un autre endroit vraiment dangereux en C/C++ opérations priorité des opérations bit à bit et de comparaison. Prendre en considération sur le fragment suivant:

int a = 2;
int b = (a == 2|1); // Looks like check for expression result? Nope, results 1!

Impatient, je vous recommande la réécriture de votre fragment de cette façon de garder l'équilibre entre l'efficacité et la lisibilité:

minx = ((x1 <= x2) ? x1 : x2);
maxx = ((x1 <= x2) ? x2 : x1);
miny = ((y1 <= y2) ? y1 : y2);
maxy = ((y1 <= y2) ? y2 : y1);

Fait plus intéressant sur ce fragment de code est le style pourrait entraîner le plus efficace de code pour certaines architectures comme le BRAS à cause de la condition de bits indicateurs du PROCESSEUR à jeu d'instructions (condition coûts de duplication de rien et de plus, les points de compilateur pour le droit des fragments de code).

1voto

Pierre Fourgeaud Points 10249

En raison de la priorité de l'opérateur:

(x1<=x2 ? minx=x1,maxx=x2 : minx=x2),maxx=x1

Vous pouvez le réparer avec:

 int x1=10, x2=20, y1=132, y2=12, minx, miny, maxx, maxy;
x1<=x2 ? (minx=x1,maxx=x2) : (minx=x2,maxx=x1);
y1<=y2 ? (miny=y1,maxy=y2) : (miny=y2,maxy=y1);
cout<<"minx="<<minx<<"\n";
cout<<"maxx="<<maxx<<"\n";
cout<<"miny="<<miny<<"\n";
cout<<"maxy="<<maxy<<"\n";
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X