Dlaczego ta instrukcja if łącząca przypisanie i kontrolę równości zwraca wartość true?

216

Myślałem o kilku błędach dla początkujących i skończyłem na jednym z ifoświadczenia. Rozszerzyłem nieco kod do tego:

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

Widziałem, że ifstwierdzenie zwraca wartość true i coutbrzmi ijak 1. Jeśli ijest przypisane 1w instrukcji if, dlaczego i == 0powrócił true?

TehMattGR
źródło
83
Chłopaki, to nie jest literówka. OP chce wiedzieć, dlaczego instrukcja if jest wprowadzana z tym kodem, ponieważ ijest ustawiona na 1.
NathanOliver
24
Czy przypisuje wynik 1 && i == 0?
JVApen
4
Sugestia dla początkujących: nie powinni używać takiej „zaawansowanej” konstrukcji języka. Po prostu przypisz zmienną osobno. Pozwoli to również uniknąć możliwych problemów z punktem sekwencyjnym. Tego rodzaju kod w praktycznym kodzie zwykle również wygląda źle.
user202729,
1
skończy się to pytaniem o rozmowę kwalifikacyjną
RAZ_Muh_Taz

Odpowiedzi:

391

Ma to związek z pierwszeństwem operatora .

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

nie jest

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

ponieważ zarówno &&i ==mają wyższy priorytet niż =. Tak naprawdę to działa

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

który przypisuje wynik 1 && (i == 0)do i. Tak więc, jeśli izaczyna się od, 0to i == 0jest true, więc 1 && truejest true(lub 1), a następnie izostaje ustawione na 1. Następnie, ponieważ 1jest to prawda, wpisujesz blok if i wypisujesz przypisaną wartość i.

NathanOliver
źródło
2
@ JörgWMittag To całkiem fajne. Podoba mi się, że zmusza cię do używania nawiasów.
NathanOliver
Zasadniczo i = !i; if (i)poprawnie napisane
Cacahuete Frito
@NathanOliver: Fortress był całkiem fajnym językiem, w którym wiele rzeczy było w porządku. (Głównym projektantem był Guy L. Steele, więc nie jest to żadną niespodzianką.) Niestety, nie został odebrany w ostatniej rundzie finansowania DARPA, a następnie został wyrzucony przez Oracle.
Jörg W Mittag
6
Oczywiście, prośba o odrobinę ostrzeżeń od kompilatora wykryłaby ten błąd,
Deduplicator
4
Każdy język, który nie zakłada, że ​​int i booleany są równoważne, również to odbierze.
rghome
16

Zakładając, że Twój kod wygląda tak:

#include <iostream>
using namespace std;

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

Wtedy to:

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

ocenia jako

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

i tak ijest ustawiony na 1.


źródło
39
Czy dodatkowy kod był naprawdę potrzebny? Wydaje się dość oczywiste, że tak by było, ponieważ inaczej by nie działało.
Modelmat
13
Nie tylko niepotrzebny dodatkowy kod. Odpowiedź nie wyjaśnia jasno pierwszeństwa operatora.
Francisco Zarabozo
34
Ponieważ jesteśmy w pociągu nitpick ... Rozumiem using namespace std!
Mateen Ulhaq
8
Istnieje dodatkowy kod - ale nadal nie jest to niepoprawny kod. Odpowiedź jest nadal prawidłowa. Oczywiście nie wyjaśnia to pierwszeństwa operatora. Ale ktoś mógłby zasugerować, aby dodać, zamiast głosować wprost w dół!
B Charles H
14
Wow, -4 jest trudny, biorąc pod uwagę, że odpowiada to poprawnie na pytanie, choć może nie optymalnie. Nie rozszerza się na pierwszeństwo operatora tak bardzo jak druga odpowiedź, ale mówi o tym wystarczająco dużo w kontekście kodu, aby każdy, kto pomyślał, że =przyszedł wcześniej, &&mógł zobaczyć problem. Ponadto tak, rozszerzenie jest obce, ale nie sądzę, żeby miało to tak duże znaczenie. Nie mogę uwierzyć, że tak niewielkie różnice powodują, że ludzie głosują od 151 do -4.
JoL
-4

Ma to związek z analizowaniem reguł od prawej do lewej. Np. Y = x + 5.
Wszystkie podwyrażenia są ważone według ważności. Dwa wyrażenia o tym samym znaczeniu są oceniane od prawej do lewej,. Strona wyrażania && jest wykonywana jako pierwsza, a następnie LHS.

Dla mnie to ma sens.

Leslie Satenstein
źródło
1
Stowarzyszenie („od prawej do lewej”) nie ma z tym nic wspólnego. Chodzi o pierwszeństwo („ważność”), a używane operatory nie mają równego pierwszeństwa.
Wyścigi lekkości na orbicie
-4

Rzeczywista odpowiedź to:

  1. Kompilator daje pierwszeństwo wartości „i == 0”, która daje wartość true.
  2. Następnie oceni i = 1 jako PRAWDA lub FAŁSZ, a ponieważ skompilowane operatory przypisania nigdy nie zawiodą (inaczej nie skompilowaliby), to również ocenia na prawdę.
  3. Ponieważ obie instrukcje są oceniane jako prawdziwe, a PRAWDA i PRAWDA jest PRAWDA, instrukcja if ma wartość PRAWDA.

Jako dowód spójrz na dane wyjściowe asm twojego kompilatora dla wprowadzonego kodu (wszystkie komentarze są moje):

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

Powyższy wynik asm pochodzi z CLANG, ale wszystkie inne kompilatory, na które patrzyłem, dawały podobne wyniki. Dotyczy to wszystkich kompilatorów w tej witrynie, bez względu na to, czy są to kompilatory czystego C, czy C ++, a wszystko to bez pragnienia zmiany trybu kompilatora (domyślnie C ++ dla kompilatorów C ++).

Zauważ, że twój kompilator tak naprawdę nie ustawił i = 1, ale i = TRUE (co oznacza dowolną 32-bitową niezerową wartość całkowitą). Jest tak, ponieważ operator && ocenia tylko, czy instrukcja ma wartość PRAWDA, czy FAŁSZ, a następnie ustawia wyniki zgodnie z tym wynikiem. Jako dowód spróbuj zmienić i = 1 na i = 2 i sam możesz zaobserwować, że nic się nie zmieni. Przekonaj się, korzystając z dowolnego kompilatora online w Eksploratorze kompilatorów

ar18
źródło
1
1) Dokumenty odwołują się do pierwszeństwa operatora C, gdy to pytanie jest oznaczone C ++. Dwa różne języki. 2a) i = 1jest operatorem przypisania [nie równoważności]; 2b) Zapewniam cię, że if (i = 0)w C i C ++ wystąpią fałszywe warunki, więc to, czy będzie to prawda, że ​​„nigdy nie zawodzi” jest nieco mylące.
TrebledJ
1
and cl, 1 ; = operator always TRUE<< popraw mnie, jeśli się mylę, ale nie widzę tutaj żadnego przypisania. Reprezentuje 1 &&część wyrażenia. Ta odpowiedź w zasadzie dotyczy false.
sykk
„Dokumenty odwołują się do pierwszeństwa operatora C, gdy to pytanie jest oznaczone C ++. Dwa różne języki” - a kiedy porównasz pierwszeństwo operatora C do C ++, jaka jest różnica między tymi dwoma? Mają takie same pierwszeństwo w odniesieniu do tego tematu, co nie jest zaskakujące, ponieważ C ++ jest bezpośrednią pochodną C (lub inaczej mówiąc, C jest podzbiorem języka C ++, więc oczywiście będą mieli wiele wspólnego, w tym pierwszeństwo). Naprawię i tak mój post, na wypadek, gdyby było to mylące.
ar18
„Popraw mnie, jeśli się mylę, ale nie widzę tu żadnego przypisania” - W takim razie pozwól mi cię poprawić! 1 jest wartością natychmiastową, a nie wynikiem jakiegokolwiek testu lub obliczenia. Jest to tak zwana „domniemana PRAWDA”. Jedyny test, który ma miejsce, dotyczy instrukcji i == 0, tzn. „Cmp dword ptr [rbp - 8], 0”. Miałbyś rację tylko, gdyby powiedział „movzx edx, 1”. Według WSZYSTKICH postów poprzedzających moje powinny być dwa porównania, ale w rzeczywistości jest tylko jedno, a dane wyjściowe asm KAŻDEGO głównego kompilatora dowodzą, że te posty są całkowicie niepoprawne.
ar18
2
Oprócz błędnego nadania pierwszeństwa (poprawne parsowanie - patrz odpowiedź NathanOliver), składasz fałszywe twierdzenie, że operator przypisania zawsze ma wartość PRAWDA. Spróbować if ( i = 0 ) { print something }. Również twoja odpowiedź jest sprzeczna; na początku mówisz, że i=1jest oceniany przed &&zastosowaniem, a na końcu mówisz, że ijest ustawiony na wynik &&operatora.
MM