Почему этот оператор if, объединяющий присваивание и проверку на равенство, возвращает true?

217

Я думал о некоторых ошибках новичка, и я закончил тем, что на ifутверждении. Я немного расширил код до этого:

int i = 0;
if (i = 1 && i == 0) {
    std::cout << i;
}

Я видел , что ifоператор возвращает верно, и это cout«S , iкак 1. Если в операторе if iназначено 1значение, почему i == 0вернули true?

TehMattGR
источник
83
Ребята, это не опечатка. ОП хочет знать, почему оператор if вводится с этим кодом, поскольку для iнего установлено значение 1.
Натан Оливер
24
Или это присваивает результат 1 && i == 0?
JVApen
4
Предложение для начинающих: им не следует использовать такую ​​«продвинутую» языковую конструкцию. Просто назначьте переменную отдельно. Это также позволит избежать возможных проблем с точкой последовательности. Этот вид кода в практическом коде также обычно выглядит плохо.
user202729
1
это должно закончиться на вопросе интервью
RAZ_Muh_Taz

Ответы:

392

Это связано с приоритетом оператора .

if (i = 1 && i == 0)

не является

if ((i = 1) && (i == 0))

потому что оба &&и ==имеют более высокий приоритет, чем =. Что это действительно работает, так это

if (i = (1 && (i == 0)))

который присваивает результат 1 && (i == 0)к i. Итак, если iначинается с, 0то i == 0есть true, то 1 && trueесть true(или 1), а затем iустанавливается в 1. Тогда, поскольку 1true, вы вводите блок if и печатаете значение, которое вы присвоили i.

NathanOliver
источник
2
@ JörgWMittag Это круто. Мне нравится, что это заставляет вас использовать скобки.
Натан Оливер
В основном, i = !i; if (i)правильно написано
Cacahuete Frito
@NathanOliver: Крепость была довольно классным языком, в котором многое было правильно. (Главным дизайнером был Гай Л. Стил, так что неудивительно.) К сожалению, он не был выбран для финального раунда финансирования DARPA, и впоследствии был одобрен Oracle.
Йорг Миттаг
6
Естественно, запрос небольшого количества предупреждений от компилятора обнаружил бы эту ошибку,
Deduplicator
4
Любой язык, который не предполагает, что целые и логические значения эквивалентны, также подхватил бы это.
rghome
16

Предполагая, что ваш код на самом деле выглядит так:

#include <iostream>
using namespace std;

int main()  {
    int i = 0;
    if (i = 1 && i == 0) {
        cout << i;
    }
}

Тогда это:

if (i = 1 && i == 0) {

оценивает как

 if (i = (1 && i == 0)) {

и так iустановлено 1.


источник
39
Был ли дополнительный код действительно необходим? Кажется довольно очевидным, что это было бы так, как иначе не было бы.
Modelmat
13
Не только лишний лишний код. Ответ не может четко объяснить приоритет оператора.
Франциско Сарабосо,
34
Так как мы в придирчивом поезде ... я вижу using namespace std!
Матин Улхак
8
Есть дополнительный код - но это еще не неправильный код. И ответ все еще правильный. Конечно, это не объясняет приоритет оператора. Но кто-то может предложить добавить его, вместо прямого голосования!
Чарльз Х
14
Вау, -4 сурово, учитывая, что это правильно отвечает на вопрос, хотя, возможно, не оптимально. Он не расширяет приоритет оператора так же, как другой ответ, но он достаточно говорит об этом в контексте кода, чтобы любой, кто думал, что это =было раньше, &&может увидеть проблему. Кроме того, да, расширение является посторонним, но я не думаю, что это имеет большое значение. Я не могу поверить, что такие незначительные различия заставляют людей голосовать 151 против -4.
JoL
-4

Это связано с разбором правил справа налево. Например, у = х + 5.
Все подвыражения взвешены по важности. Два выражения одинаковой важности оцениваются справа налево. Сначала выполняется сторона выражения &&, затем LHS.

Имеет смысл для меня.

Лесли Сатенштейн
источник
1
Ассоциативность («правила справа налево») не имеет к этому никакого отношения. Речь идет о приоритете («важности»), и используемые операторы не имеют одинакового приоритета.
Гонки
-4

Фактический ответ:

  1. Компилятор отдает приоритет "i == 0", что соответствует истине.
  2. Затем он будет оценивать i = 1 как TRUE или FALSE, и поскольку скомпилированные операторы присваивания никогда не завершаются ошибкой (иначе они не будут компилироваться), он также оценивается как true.
  3. Поскольку оба оператора оцениваются как истинные, а TRUE && TRUE оценивается как TRUE, оператор if будет оцениваться как TRUE.

В качестве доказательства, просто посмотрите на вывод asm вашего компилятора для кода, который вы ввели (все комментарии мои):

mov     dword ptr [rbp - 8], 0    ; i = 0;
cmp     dword ptr [rbp - 8], 0    ; i == 0?
sete    al                        ; TRUE (=1)
mov     cl, al
and     cl, 1                     ; = operator always TRUE
movzx   edx, cl
mov     dword ptr [rbp - 8], edx  ; set i=TRUE;
test    al, 1                     ; al never changed,
                                  ; so final ans is TRUE

Вывод asm выше был из CLANG, но все остальные компиляторы, на которые я смотрел, давали похожий вывод. Это верно для всех компиляторов на этом сайте, будь то чистые компиляторы C или C ++, все без каких-либо прагм для изменения режима компилятора (который по умолчанию является C ++ для компиляторов C ++)

Обратите внимание, что ваш компилятор на самом деле не установил i = 1, но i = TRUE (что означает любое 32-битное не нулевое целое значение). Это связано с тем, что оператор && только оценивает, является ли оператор TRUE или FALSE, а затем устанавливает результаты в соответствии с этим результатом. В качестве доказательства попробуйте изменить i = 1 на i = 2, и вы сами сможете убедиться, что ничего не изменится. Убедитесь сами, используя любой онлайн-компилятор в Compiler Explorer

ar18
источник
1
1) Документы ссылаются на приоритет оператора C, когда этот вопрос помечен как C ++. Два разных языка. 2a) i = 1является оператором присваивания [не эквивалентности]; 2b) Я могу заверить вас, что if (i = 0)в C и C ++ будет выполнено ложное условие, поэтому то, что оно оценивается как истинное по отношению к «никогда не выходит из строя», несколько обманчиво.
TrebledJ
1
and cl, 1 ; = operator always TRUE<< поправьте меня, если я ошибаюсь, но я не вижу здесь никакого назначения. Он представляет собой 1 &&часть выражения. Так что этот ответ в основном оценивается как false.
Сик
«Документы ссылаются на приоритет оператора C, когда этот вопрос помечен C ++. Два разных языка» - и когда вы сравниваете приоритет оператора C с C ++, в чем разница между ними? Они имеют одинаковый приоритет в отношении этой темы, что неудивительно, поскольку C ++ является прямой производной от C (или, если можно так выразиться, C является подмножеством языка C ++, поэтому, конечно, они будут иметь много общего, в том числе приоритет). Я исправлю свой пост в любом случае, в случае, если это сбивает с толку.
ar18
«Поправь меня, если я ошибаюсь, но я не вижу здесь никакого назначения» - тогда позволь мне исправить тебя! 1 является непосредственным значением, а не результатом какого-либо теста или расчета. Это то, что называется «предполагаемым ИСТИННЫМ» значением. Единственный тест, который имеет место, для оператора i == 0, то есть - "cmp dword ptr [rbp - 8], 0". Вы были бы правы, только если бы он сказал "movzx edx, 1". Согласно ВСЕМ постам, предшествующим моему, должно быть два сравнения, но в реальной жизни есть только одно, и вывод asm КАЖДОГО основного компилятора доказывает, что эти посты абсолютно неверны.
ar18
2
Помимо неправильного определения приоритета (правильный ответ синтаксического анализа см. В ответе NathanOliver), вы делаете ложное утверждение о том, что оператор присваивания всегда переходит в TRUE. Попробуй if ( i = 0 ) { print something }. Также ваш ответ противоречит самому себе; в начале вы говорите, что i=1оценивается до того, &&как применяется, а затем в конце вы говорите, что iустановлено на результат &&оператора.
MM