Por que essa declaração if que combina atribuição e verificação de igualdade é verdadeira?

216

Eu estive pensando em alguns erros para iniciantes e acabei com o que estava na ifdeclaração. Expandi um pouco o código para isso:

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

Eu vi que a ifafirmação retorna verdadeira, e couté icomo 1. If ié atribuído 1na instrução if, por que i == 0retornou true?

TehMattGR
fonte
83
Gente, isso não é uma pergunta de erro de digitação. O OP quer saber por que a instrução if é inserida com este código, pois iestá definida como 1.
NathanOliver 22/05/19
24
Ou atribui o resultado de 1 && i == 0?
JVApen
4
Sugestão para iniciantes: eles não devem usar essa construção de linguagem "avançada". Apenas atribua a variável separadamente. Isso também evitará possíveis problemas com o ponto de sequência. Esse tipo de código no código prático geralmente também parece ruim.
User202729 de
1
este é obrigado a acabar em uma pergunta da entrevista
RAZ_Muh_Taz

Respostas:

391

Isso tem a ver com precedência do operador .

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

não é

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

porque ambos &&e ==têm uma precedência maior que =. O que realmente funciona é

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

que atribui o resultado de 1 && (i == 0)a i. Portanto, se icomeça em 0então , i == 0é true, também 1 && trueé true(ou 1) e ié definido como 1. Então, como 1é verdade, você insere o bloco if e imprime o valor atribuído i.

NathanOliver
fonte
2
@ JörgWMittag Isso é muito legal. Eu gosto que isso o força a usar parênteses.
NathanOliver 24/05/19
Basicamente, i = !i; if (i)escrito corretamente
Cacahuete Frito
@ NathanOliver: Fortress era uma linguagem muito legal que acertou muitas coisas. (O designer principal foi Guy L. Steele, portanto não há surpresa.) Infelizmente, ele não foi escolhido para a rodada final de financiamento da DARPA e, posteriormente, foi prejudicado pela Oracle.
Jörg W Mittag
6
Naturalmente, pedindo um pouco de avisos do compilador iria ter detectado esse erro,
Deduplicator
4
Qualquer linguagem que não assuma ints e booleanos é equivalente também entenderia isso.
Rghome 24/05/19
16

Assumindo que seu código realmente se parece com isso:

#include <iostream>
using namespace std;

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

Então isso:

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

avalia como

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

e assim iestá definido como 1.


fonte
39
O código extra era realmente necessário? Parece bastante óbvio que esse seria o caso, pois não seria executado de outra forma.
Modelmat 23/05/19
13
Não apenas código extra desnecessário. A resposta falha ao explicar claramente a precedência do operador.
Francisco Zarabozo 23/05/19
34
Desde que estamos no trem nitpick ... eu vejo um using namespace std!
Mateen Ulhaq 23/05/19
8
Há código extra - mas ainda não é um código incorreto. E a resposta ainda está certa. Obviamente, isso não explica a precedência do operador. Mas alguém poderia sugerir que fosse adicionado, em vez de uma votação direta!
B Charles H
14
Uau, -4 é duro, considerando que isso responde à pergunta corretamente, embora talvez não seja ideal. Ele não expande a precedência do operador tanto quanto a outra resposta, mas diz o suficiente sobre isso no contexto do código, para que qualquer um que pensou que =veio antes &&possa ver o problema. Além disso, sim, a expansão é estranha, mas não acho que isso importe muito. Não acredito que essas pequenas diferenças levem as pessoas a votar 151 a -4.
23419 JoL
-4

Tem a ver com a análise de regras da direita para a esquerda. Por exemplo, y = x + 5.
Todas as subexpressões são ponderadas em importância. Duas expressões de igual importância são avaliadas da direita para a esquerda,. O lado da expressão && é feito primeiro, seguido pelo LHS.

Faz sentido para mim.

Leslie Satenstein
fonte
1
A associatividade ("regras da direita para a esquerda") não tem nada a ver com isso. É sobre precedência ("importância"), e os operadores usados não têm precedência igual.
Lightness Races em órbita
-4

A resposta real é:

  1. O compilador dá precedência para "i == 0", que é avaliado como verdadeiro.
  2. Em seguida, ele avaliará i = 1 como VERDADEIRO ou FALSO, e como os operadores de atribuição compilados nunca falham (caso contrário, não compilariam), ele também avalia como verdadeiro.
  3. Como as duas instruções são avaliadas como verdadeiras e TRUE && TRUE é avaliada como TRUE, a instrução if será avaliada como TRUE.

Como prova, basta olhar para a saída asm do seu compilador para o código digitado (todos os comentários são meus):

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

A saída asm acima era do CLANG, mas todos os outros compiladores que eu analisei deram saída semelhante. Isso é verdadeiro para todos os compiladores nesse site, sejam eles compiladores C ou C ++ puros, todos sem pragmas para alterar o modo do compilador (que por padrão é C ++ para os compiladores C ++)

Observe que seu compilador não definiu i = 1, mas i = TRUE (que significa qualquer valor inteiro de 32 bits e não zero). Isso ocorre porque o operador && avalia apenas se uma instrução é VERDADEIRA ou FALSA e, em seguida, define os resultados de acordo com esse resultado. Como prova, tente alterar i = 1 para i = 2 e observe por si mesmo que nada mudará. Veja você mesmo usando qualquer compilador online no Compiler Explorer

ar18
fonte
1
1) Os documentos vinculam-se à precedência do operador C, quando esta pergunta é marcada com C ++. Dois idiomas diferentes 2a) i = 1é um operador de atribuição [não uma equivalência]; 2b) Posso garantir que você if (i = 0)avaliará a condição falsa em C e C ++, portanto, se é avaliado como verdadeiro "nunca falha", é um pouco enganador.
TrebledJ
1
and cl, 1 ; = operator always TRUE<< corrija-me se estiver errado, mas não vejo nenhuma atribuição aqui. Representa a 1 &&parte da expressão. Portanto, essa resposta é basicamente avaliada como false.
syck
"Os documentos vinculam-se à precedência do operador C, quando esta pergunta é marcada com C ++. Duas linguagens diferentes" - e quando você compara a precedência do operador C com C ++, qual é a diferença entre os dois? Eles têm a mesma precedência em relação a este tópico, o que não é surpreendente, pois o C ++ é um derivado direto do C (ou de outra forma, C é um subconjunto da linguagem C ++, portanto, é claro que eles terão muito em comum, incluindo precedência). De qualquer maneira, vou consertar minha postagem, caso isso seja confuso.
Ar18
"Corrija-me se estiver errado, mas não vejo nenhuma atribuição aqui" - Então deixe-me corrigi-lo! O 1 é um valor imediato e não o resultado de qualquer teste ou cálculo. É o que é chamado de valor "presumido VERDADEIRO". O único teste que ocorre é para a instrução i == 0, ou seja, - "cmp dword ptr [rbp - 8], 0". Você só estaria correto se tivesse dito "movzx edx, 1". De acordo com TODAS as postagens anteriores à minha, deve haver duas comparações, mas na vida real há apenas uma, e a saída ASM de TODOS OS principais compiladores prova que essas postagens estão completamente incorretas.
Ar18
2
Além de errar a precedência (consulte a resposta do NathanOliver para a análise correta), você faz a afirmação falsa de que o operador de atribuição sempre avalia como TRUE. Tente if ( i = 0 ) { print something }. Também a sua resposta se contradiz; no início, você diz que i=1é avaliado antes da &&aplicação e, no final, diz que iestá definido como o resultado do &&operador.
MM