Подтвердить что ты не робот

Получил неожиданный ответ от выражения x? y: z

Вот простой фрагмент С++:

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";

Я думал, что результат должен быть:

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

Но на самом деле результат:

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

Может ли кто-нибудь дать объяснение, почему maxx не 20? Спасибо.

4b9b3361

Ответ 1

Из-за приоритета оператора выражение анализируется следующим образом:

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

вы можете решить это с помощью

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

И на самом деле вам не нужны первые две пары круглых скобок. Также проверьте этот вопрос.

Ответ 2

Приоритет условного оператора больше, чем у оператора с запятой, поэтому

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

заключен в скобки как

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

Таким образом, последнее назначение выполняется независимо от условия.

Чтобы исправить это, вы можете

  • использовать круглые скобки:

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

    (вам не нужны скобки в ветке true, но им тоже нужно иметь их).

  • используйте два условных выражения:

    minx = x1 <= x2 ? x1 : x2;
    maxx = x1 <= x2 ? x2 : x1;
    
  • используйте if:

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

Скомпилированный с оптимизацией или без нее, версия if и заключенные в скобки отдельные условия с запятыми производят одну и ту же сборку как в gcc (4.7.2), так и в clang (3.2), разумно ожидать, что и от других компиляторов тоже. Версия с двумя условными выражениями создает разную сборку, но с оптимизацией оба этих компилятора испускают для этого только одну инструкцию cmp.

На мой взгляд, версия if проще всего проверять правильность, поэтому предпочтительнее.

Ответ 3

В то время как другие объяснили, в чем причина проблемы, я думаю, что "лучшим" решением должно быть написать условие с if:

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

Да, это несколько строк дольше, но также легче читать и четко понимать, что происходит (и если вам нужно пройти через него в отладчике, вы можете легко увидеть, в каком направлении он идет).

Любой современный компилятор должен иметь возможность конвертировать любой из них в довольно эффективные условные назначения, которые хорошо справляются с удалением ветвей (и, следовательно, "плохое предсказание ветвей" ).

Я подготовил небольшой тест, который я скомпилировал с помощью

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

Здесь источник (я должен был сделать его параметры, чтобы компилятор не просто вычислил правильное значение напрямую и просто сделал mov $12,%rdx, но на самом деле сравнил и решил с ним больше):

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

Сгенерированный код выглядит следующим образом:

_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.... 

Как вы можете видеть, mine и original идентичны, а romano использует несколько разные регистры и другую форму cmov, но в остальном они делают то же самое в одном и том же количестве инструкций.

Ответ 4

Интересный вопрос как о приоритете операций, так и генерации кода.

ОК, , операция имеет ОЧЕНЬ низкий приоритет (самая низкая, см. справочная таблица). Из-за этого ваш код будет таким же, как в следующих строках:

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

На самом деле только C/С++-грамматика предотвращает первый , от того же поведения.

Еще одно действительно опасное место в приоритете операций C/С++ - это побитовые операции и сравнение. Рассмотрим следующий фрагмент:

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

В ожидании, я бы рекомендовал переписывать фрагмент таким образом, сохраняя баланс между эффективностью и удобочитаемостью:

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

Наиболее интересным фактом об этом фрагменте кода является такой стиль, который может привести к наиболее эффективному коду для некоторых архитектур, таких как ARM, из-за флагов состояния бит в наборе инструкций процессора (условие дублирования ничего не стоит и больше, указывает на компилятор в нужные фрагменты кода).

Ответ 5

Из-за приоритета оператора:

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

Вы можете исправить это с помощью

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";

Ответ 6

В С++ 11 вы можете использовать std::tie и std::make_pair, чтобы сделать это явно-правильно-на-взгляд (TM)

#include <tuple>
#include <utility>
#include <iostream>

using namespace std;

int main()
{
    int x1 = 10, x2=20, y1=132, y2=12, minx, miny, maxx, maxy;

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

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

В Интернете вывод.

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

  • минимум повторения кода,
  • 4 назначенных переменных находятся в левой части задания, а
  • все четыре назначенные переменные находятся справа.

Как небольшое изменение, которое обобщает на поиск указателей на элемент последовательности min и max, вы можете использовать std::minmax_element и тот факт, что необработанные массивы имеют функции, отличные от членов begin() и end() (оба С++ 11 функций)

#include <algorithm>
#include <tuple>
#include <iostream>

using namespace std;

int main()
{
    int x[] = { 10, 20 }, y[] = { 132, 12 }, *minx, *miny, *maxx, *maxy;

    tie(minx, maxx) = minmax_element(begin(x), end(x));
    tie(miny, maxy) = minmax_element(begin(y), end(y));

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

Online выход.