¿Por qué esta afirmación if que combina la asignación y una verificación de igualdad devuelve verdadero?

216

He estado pensando en algunos errores de principiante y terminé con el de la ifdeclaración. Expandí un poco el código a esto:

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

He visto que la ifdeclaración devuelve verdadero, y coutes icomo 1. Si ise asigna 1en la declaración if, ¿por qué i == 0regresó true?

TehMattGR
fuente
83
Chicos, esta no es una pregunta tipográfica. El OP quiere saber por qué la instrucción if se ingresa con este código, ya que ise establece en 1.
NathanOliver
24
¿O asigna el resultado de 1 && i == 0?
JVApen
44
Sugerencia para principiantes: no deben usar tal construcción de lenguaje "avanzado". Simplemente asigne la variable por separado. Eso también evitará posibles problemas con el punto de secuencia. Este tipo de código en el código práctico generalmente también se verá mal.
user202729
1
esto terminará en una pregunta de entrevista
RAZ_Muh_Taz

Respuestas:

391

Esto tiene que ver con la precedencia del operador .

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

no es

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

porque ambos &&y ==tienen una mayor precedencia que =. Lo que realmente funciona es

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

que asigna el resultado de 1 && (i == 0)a i. Entonces, si icomienza en, 0entonces i == 0es true, también lo 1 && truees true(o 1), y luego ise establece en 1. Entonces, dado que 1es verdadero, ingrese el bloque if e imprima el valor que le asignó i.

NathanOliver
fuente
2
@ JörgWMittag Eso es muy bueno. Me gusta que te obliga a usar paréntesis.
NathanOliver
Básicamente, i = !i; if (i)escrito correctamente
Cacahuete Frito
@NathanOliver: Fortress era un lenguaje genial que acertó en muchas cosas. (El diseñador principal fue Guy L. Steele, por lo que no sorprende). Desafortunadamente, no fue elegido para la ronda final de financiamiento de DARPA, y posteriormente Oracle lo rechazó.
Jörg W Mittag
66
Naturalmente, pedir un mínimo de advertencias del compilador habría detectado ese error,
Deduplicator
44
Cualquier lenguaje que no asuma que los ints y booleanos son equivalentes también lo recogerá.
rghome
16

Asumiendo que su código en realidad se ve así:

#include <iostream>
using namespace std;

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

Luego esto:

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

evalúa como

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

y así ise establece en 1.


fuente
39
¿Era realmente necesario el código extra? Parece bastante obvio que este sería el caso, ya que de lo contrario no se ejecutaría.
Modelmat
13
No solo código adicional innecesario. La respuesta no explica claramente la precedencia del operador.
Francisco Zarabozo
34
Ya que estamos en el tren de la trampa ... ¡veo un using namespace std!
Mateen Ulhaq
8
Hay código adicional, pero todavía no es un código incorrecto. Y la respuesta sigue siendo correcta. Por supuesto, no explica la precedencia del operador. ¡Pero alguien podría sugerir que se agregue, en lugar de votar directamente!
B Charles H
14
Wow, -4 es duro, considerando que esto responde la pregunta correctamente, aunque quizás no de manera óptima. No se expande tanto en la precedencia del operador como en la otra respuesta, pero sí dice lo suficiente en el contexto del código para que cualquiera que haya pensado =antes &&pueda ver el problema. Además, sí, la expansión es extraña, pero no creo que importe tanto. No puedo creer que diferencias tan pequeñas hagan que la gente vote 151 a -4.
JoL
-4

Tiene que ver con analizar y las reglas de derecha a izquierda. Por ejemplo, y = x + 5.
Todas las sub-expresiones son ponderadas en importancia. Dos expresiones de igual importancia se evalúan de derecha a izquierda,. El lado de expresión && se realiza primero, seguido del LHS.

Tiene sentido para mi.

Leslie Satenstein
fuente
1
La asociatividad ("reglas de derecha a izquierda") no tiene nada que ver con esto. Se trata de precedencia ("importancia"), y los operadores utilizados no tienen la misma precedencia.
Carreras de ligereza en órbita
-4

La respuesta real es:

  1. El compilador da prioridad a "i == 0", que se evalúa como verdadero.
  2. Luego evaluará i = 1 como VERDADERO o FALSO, y dado que los operadores de asignación compilados nunca fallan (de lo contrario no compilarían), también se evalúa como verdadero.
  3. Dado que ambas declaraciones se evalúan como verdaderas, y VERDADERO && VERDADERO se evalúa como VERDADERO, la declaración if se evaluará como VERDADERO.

Como prueba, solo mira la salida asm de tu compilador para el código que ingresaste (todos los comentarios son míos):

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

La salida asm anterior era de CLANG, pero todos los otros compiladores que miré dieron una salida similar. Esto es cierto para todos los compiladores en ese sitio, ya sean compiladores C o C ++ puros, todo sin ningún pragma para cambiar el modo del compilador (que por defecto es C ++ para los compiladores C ++)

Tenga en cuenta que su compilador en realidad no estableció i = 1, pero i = TRUE (lo que significa cualquier valor entero no cero de 32 bits). Esto se debe a que el operador && solo evalúa si una declaración es VERDADERA o FALSA, y luego establece los resultados de acuerdo con ese resultado. Como prueba, intente cambiar i = 1 a i = 2 y podrá observar por sí mismo que nada cambiará. Compruébelo usted mismo utilizando cualquier compilador en línea en Compiler Explorer

ar18
fuente
1
1) Los documentos enlazan con la precedencia del operador C, cuando esta pregunta está etiquetada con C ++. Dos idiomas diferentes 2a) i = 1es un operador de asignación [no de equivalencia]; 2b) Puedo asegurarle que if (i = 0)se evaluará en condición falsa tanto en C como en C ++, por lo que si se evalúa como verdadero wrt "nunca falla" es algo engañoso.
TrebledJ
1
and cl, 1 ; = operator always TRUE<< corrígeme si estoy equivocado, pero no veo ninguna asignación aquí. Representa la 1 &&parte de la expresión. Entonces, esta respuesta básicamente se evalúa como false.
syck
"Los documentos se vinculan con la precedencia del operador C, cuando esta pregunta está etiquetada con C ++. Dos lenguajes diferentes", y cuando se compara la precedencia del operador C con C ++, ¿cuál es la diferencia entre los dos? Tienen la misma precedencia con respecto a este tema, lo cual no es sorprendente, ya que C ++ es una derivada directa de C (u otra forma de decirlo es que C es un subconjunto del lenguaje C ++, por lo que, por supuesto, tendrán mucho en común, incluida la precedencia). Arreglaré mi publicación de todos modos, en caso de que sea confuso.
ar18
"Corrígeme si estoy equivocado, pero no veo ninguna asignación aquí" - ¡Entonces déjame corregirte! El 1 es un valor inmediato y no el resultado de ninguna prueba o cálculo. Es lo que se llama un valor "presunto VERDADERO". La única prueba que se realiza es para la instrucción i == 0, es decir, "cmp dword ptr [rbp - 8], 0". Solo sería correcto si hubiera dicho "movzx edx, 1". De acuerdo con TODAS las publicaciones anteriores a la mía, debería haber dos comparaciones, pero en la vida real solo hay una, y la salida asm de CADA compilador principal demuestra que esas publicaciones son completamente incorrectas.
ar18
2
Además de equivocarse en la precedencia (consulte la respuesta de NathanOliver para el análisis correcto), hace la afirmación falsa que el operador de asignación siempre evalúa como VERDADERO. Tratar if ( i = 0 ) { print something }. También su respuesta se contradice a sí misma; al principio dices que i=1se evalúa antes de que &&se aplique, y luego al final dices que ise ajusta al resultado del &&operador.
MM