Perché questa istruzione if che unisce l'assegnazione e un controllo di uguaglianza ritorna vera?


216

Ho pensato ad alcuni errori per principianti e ho finito con quello sulla ifdichiarazione. Ho ampliato un po 'il codice a questo:

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

Ho visto che le ifistruzione restituisce vero, ed è couts' icome 1. Se iviene assegnato 1nell'istruzione if, perché è i == 0tornato true?


83
Ragazzi, questa non è una domanda errata. L'OP vuole sapere perché l'istruzione if è stata immessa con questo codice poiché iè impostata su 1.
NathanOliver,

24
O assegna il risultato di 1 && i == 0?
JVApen,

4
Suggerimento per i principianti: non dovrebbero usare tale costrutto linguistico "avanzato". Basta assegnare la variabile separatamente. Ciò eviterà anche possibili problemi con il punto sequenza. Questo tipo di codice in codice pratico di solito sembra anche male.
user202729,

1
questo è destinato a finire in un'intervista
RAZ_Muh_Taz

Risposte:


391

Questo ha a che fare con la precedenza dell'operatore .

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

non è

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

perché entrambi &&e ==hanno una precedenza superiore a =. Ciò che funziona davvero è

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

che assegna il risultato di 1 && (i == 0)a i. Quindi, se iinizia da 0allora i == 0è true, così 1 && trueè true(o 1), e quindi iviene impostato su 1. Quindi, poiché 1è vero, inserisci il blocco if e stampa il valore a cui ti sei assegnato i.


2
@ JörgWMittag È fantastico. Mi piace che ti costringa a usare le parentesi.
NathanOliver,

Fondamentalmente, i = !i; if (i)correttamente scritto
Cacahuete Frito

@NathanOliver: Fortress era un linguaggio piuttosto interessante che ha fatto un sacco di cose. (Il lead designer era Guy L. Steele, quindi nessuna sorpresa lì.) Sfortunatamente, non è stato raccolto per il round finale dei finanziamenti DARPA, e successivamente è stato imbrattato da Oracle.
Jörg W Mittag

6
Naturalmente, la richiesta di un minimo di avvertimenti dal compilatore avrebbe rilevato quell'errore,
Deduplicator

4
Qualsiasi lingua che non presupponga che integri e booleani siano equivalenti lo prenderebbe anche.
rghome

16

Supponendo che il tuo codice effettivamente assomigli a questo:

#include <iostream>
using namespace std;

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

Poi questo:

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

valuta come

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

e così iè impostato su 1.


39
Il codice extra era davvero necessario? Sembra abbastanza ovvio che questo sarebbe il caso in quanto non avrebbe funzionato diversamente.
Modelmat

13
Non solo codice aggiuntivo non necessario. La risposta non spiega chiaramente la precedenza dell'operatore.
Francisco Zarabozo,

34
Dato che siamo sul treno nitpick ... vedo un using namespace std!
Mateen Ulhaq,

8
C'è un codice aggiuntivo, ma non è ancora un codice errato. E la risposta è ancora giusta. Naturalmente, non spiega la precedenza dell'operatore. Ma qualcuno potrebbe suggerire di aggiungerlo, invece di votare definitivamente!
B Charles H,

14
Wow, -4 è duro, considerando che risponde alla domanda correttamente, anche se forse non in modo ottimale. Non si espande in base alla precedenza dell'operatore tanto quanto l'altra risposta, ma ne dice abbastanza nel contesto del codice in modo che chiunque abbia pensato che sia =venuto prima &&possa vedere il problema. Inoltre, sì, l'espansione è estranea, ma non penso che sia così importante. Non posso credere che differenze così lievi inducano le persone a votare da 151 a -4.
JoL

-4

Ha a che fare con l'analisi delle regole da destra a sinistra. Ad esempio y = x + 5.
Tutte le sottoespressioni sono ponderate per importanza. Due espressioni di uguale importanza sono valutate da destra a sinistra,. L'espressione && viene eseguita per prima, seguita da LHS.

Ha senso per me.


1
L'associatività ("regole da destra a sinistra") non ha nulla a che fare con questo. Riguarda la precedenza ("importanza") e gli operatori utilizzati non hanno la stessa precedenza.
Razze di leggerezza in orbita

-4

La risposta effettiva è:

  1. Il compilatore dà la precedenza a "i == 0", che restituisce true.
  2. Quindi valuterà i = 1 come TRUE o FALSE, e poiché gli operatori di assegnazione compilati non falliscono mai (altrimenti non si compilerebbero), valuta anche su true.
  3. Poiché entrambe le istruzioni vengono valutate come vere e TRUE && TRUE restituisce VERO, l'istruzione if verrà valutata come VERA.

Come prova, basta guardare l'output asm del compilatore per il codice che hai inserito (tutti i commenti sono miei):

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

L'output di cui sopra era di CLANG, ma tutti gli altri compilatori che ho visto hanno prodotto un output simile. Questo vale per tutti i compilatori su quel sito, siano essi compilatori C o C ++ puri, tutti privi di pragmi per modificare la modalità del compilatore (che per impostazione predefinita è C ++ per i compilatori C ++)

Nota che il tuo compilatore non ha effettivamente impostato i = 1, ma i = TRUE (il che significa che qualsiasi valore intero a 32 bit non zero). Questo perché l'operatore && valuta solo se un'istruzione è TRUE o FALSE, quindi imposta i risultati in base a quel risultato. Come prova, prova a cambiare i = 1 in i = 2 e puoi osservare tu stesso che nulla cambierà. Guarda tu stesso usando qualsiasi compilatore online su Compiler Explorer


1
1) I documenti si collegano alla precedenza dell'operatore C, quando questa domanda è taggata con C ++. Due lingue diverse. 2a) i = 1è un operatore di assegnazione [non di equivalenza]; 2b) Posso assicurarti che if (i = 0)valuterà la condizione falsa sia in C che in C ++, quindi se si valuta in vero wrt "non fallisce mai" è in qualche modo fuorviante.
TrebledJ

1
and cl, 1 ; = operator always TRUE<< correggimi se sbaglio, ma non vedo alcuna assegnazione qui. Rappresenta la 1 &&parte dell'espressione. Quindi questa risposta valuta sostanzialmente quella false.
Syck

"I documenti si collegano alla precedenza dell'operatore C, quando questa domanda è taggata con C ++. Due lingue diverse" - e quando si confronta la precedenza dell'operatore C con C ++, qual è la differenza tra i due? Hanno la stessa precedenza rispetto a questo argomento, il che non sorprende, visto che C ++ è una derivata diretta di C (o un altro modo di dirlo è, C è un sottoinsieme del linguaggio C ++, quindi ovviamente avranno molto in comune, compresa la precedenza). Risolverò comunque il mio post, nel caso sia confuso.
ar18,

"Correggimi se sbaglio, ma non vedo nessuna assegnazione qui" - Allora lascia che ti corregga! 1 è un valore immediato e non il risultato di alcun test o calcolo. È quello che viene chiamato un valore "presunto VERO". L'unico test che si svolge è per l'istruzione i == 0, ovvero - "cmp dword ptr [rbp - 8], 0". Avresti ragione solo se avesse detto "movzx edx, 1". Secondo TUTTI i post che precedono il mio, ci dovrebbero essere due confronti, ma nella vita reale ce n'è solo uno e l'output di ASMY di tutti i principali compilatori dimostra che questi post sono completamente errati.
ar18,

2
Oltre a sbagliare la precedenza (vedi la risposta di NathanOliver per l'analisi corretta), fai la falsa affermazione che l'operatore di assegnazione valuta sempre su VERO. Prova if ( i = 0 ) { print something }. Anche la tua risposta si contraddice; all'inizio dici che i=1viene valutato prima che &&venga applicato, e poi alla fine dici che iè impostato sul risultato &&dell'operatore.
MM
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.