Expresión + (+ k--) en C

9

Vi esta pregunta en una prueba en la que tenemos que decirle a la salida del siguiente código.

#include<stdio.h>

int main(){
    int k = 0;
    while(+(+k--)!=0)
    k=k++;
    printf("%d\n", k);  
    return 0;
}

La salida es -1. Sin embargo, no estoy seguro de por qué esta es la respuesta.

¿Qué significa la expresión +(+k--)en C?

Ankur Gautam
fuente
44
¿Fue formateado así? Eso es malo ;-)
Peter - Restablece a Monica el
3
Sugerencia: nunca escriba código BS como este en la vida real.
Jabberwocky el
55
Esto no es UB. Mira mi respuesta.
dbush
@ Peter-ReinstateMonica Sí, fue formateado así.
Ankur Gautam
2
Debido a su naturaleza artificial y giros extraños, k=k++no está definido, pero no está indefinido porque nunca se ejecutó debido a una condición ofuscada, estoy votando para cerrar como un duplicado de esta pregunta .
Steve Summit, el

Respuestas:

10

[Para el registro, he editado esta respuesta bastante significativamente desde que fue aceptada y votada. Sin embargo, todavía dice básicamente las mismas cosas.]

Este código es profundamente, quizás deliberadamente, confuso. Contiene una instancia estrechamente evitada del comportamiento temible indefinido . Es básicamente imposible determinar si la persona que formuló esta pregunta era muy, muy inteligente o muy, muy estúpida. Y la "lección" que este código puede pretender enseñar o cuestionar sobre usted, es decir, que el operador unario plus no hace mucho, ciertamente no es lo suficientemente importante como para merecer este tipo de dirección subversiva.

Hay dos aspectos confusos del código, la extraña condición:

while(+(+k--)!=0)

y la declaración demente que controla:

k=k++;

Voy a cubrir la segunda parte primero.

Si tiene una variable como kesa que desea incrementar en 1, C le proporciona no una, ni dos, ni tres, sino cuatro formas diferentes de hacerlo:

  1. k = k + 1
  2. k += 1
  3. ++k
  4. k++

A pesar de esta recompensa (o quizás por eso), algunos programadores se confunden y emiten contorsiones como

k = k++;

Si no puede averiguar qué se supone que debe hacer, no se preocupe: nadie puede hacerlo. Esta expresión contiene dos intentos diferentes de alterar kel valor (la k =parte y la k++parte), y debido a que no hay una regla en C para decir cuál de las modificaciones intentadas "gana", una expresión como esta está formalmente indefinida , lo que significa no solo eso no tiene un significado definido, pero que todo el programa que lo contiene es sospechoso.

Ahora, si observa con mucho cuidado, verá que en este programa en particular, la línea en k = k++realidad no se ejecuta, porque (como estamos a punto de ver) la condición de control es inicialmente falsa, por lo que el ciclo se ejecuta 0 veces . Por lo que este programa en particular no podría en realidad ser indefinido - pero sigue siendo confuso patológicamente.

Consulte también estas respuestas SO canónicas a todas las preguntas relacionadas con el comportamiento indefinido de este tipo.

Pero no preguntaste sobre el k=k++papel. Preguntaste sobre la primera parte confusa, la +(+k--)!=0condición. Esto se ve extraño, porque es extraño. Nadie jamás jamás escribiría dicho código en un programa real. Entonces no hay razón para aprender cómo entenderlo. (Sí, es cierto, explorar los límites de un sistema puede ayudarlo a aprender sobre sus puntos finos, pero en mi libro hay una línea bastante clara entre exploraciones imaginativas y sugerentes versus exploraciones malintencionadas y abusivas, y esta expresión es muy clara en el lado equivocado de esa línea.)

De todos modos, examinemos +(+k--)!=0. (Y después de hacerlo, olvidémonos de todo). Cualquier expresión como esta debe entenderse de adentro hacia afuera. Supongo que sabes qué

k--

hace. Toma kel valor actual y lo "devuelve" al resto de la expresión, y disminuye más o menos simultáneamente k, es decir, almacena la cantidad k-1nuevamente k.

Pero entonces, ¿qué hace el +? Esto es unario más, no binario más. Es como el unario menos. Sabes que el binario menos hace resta: la expresión

a - b

resta b de a. Y sabes que el unario menos niega las cosas: la expresión

-a

te da el negativo de a. Lo que +hace unario es ... básicamente nada. +ale da ael valor, después de cambiar los valores positivos a valores positivos y negativos a negativos. Entonces la expresión

+k--

te da lo que sea que te haya k--dado, es decir, kel viejo valor.

Pero no hemos terminado, porque tenemos

+(+k--)

Esto solo toma lo que +k--te dio, y se aplica +de nuevo a él. Entonces te da lo que te +k--dio, que fue lo que k--te dio, que era kel valor anterior.

Entonces, al final, la condición

while(+(+k--)!=0)

hace exactamente lo mismo que la condición mucho más común

while(k-- != 0)

habría hecho. (También hace lo mismo que la condición de aspecto aún más complicado while(+(+(+(+k--)))!=0)hubiera hecho. Y esos paréntesis no son realmente necesarios; también hace lo mismo que while(+ + + +k--!=0)hubiera hecho).

Incluso averiguar cuál es la condición "normal"

while(k-- != 0)

hace es un poco complicado. Hay dos cosas que suceden en este ciclo: a medida que el ciclo se ejecuta potencialmente varias veces, vamos a:

  1. seguir haciendo k--, para hacer kcada vez más pequeño, pero también
  2. sigue haciendo el cuerpo del bucle, lo que sea que haga.

Pero hacemos la k--parte de inmediato, antes (o en el proceso de) decidir si hacer otro viaje a través del ciclo. Y recuerde que k--"devuelve" el valor anterior de k, antes de disminuirlo. En este programa, el valor inicial de kes 0. Por k--lo tanto, va a "devolver" el valor anterior 0, luego actualizar ka -1. Pero el resto de la condición es != 0, pero como acabamos de ver, la primera vez que probamos la condición, obtuvimos un 0. Así que no haremos ningún viaje a través del ciclo, por lo que no intentaremos ejecutar el declaración problemática k=k++en absoluto.

En otras palabras, en este ciclo particular, aunque dije que "están ocurriendo dos cosas", resulta que la cosa 1 sucede una vez, pero la cosa 2 ocurre cero veces.

En cualquier caso, espero que ahora esté suficientemente claro por qué esta pobre excusa para un programa termina imprimiendo -1 como el valor final de k. Normalmente, no me gusta responder preguntas de preguntas como esta, se siente como hacer trampa, pero en este caso, dado que estoy tan en desacuerdo con todo el punto del ejercicio, no me importa.

Steve Summit
fuente
1
Los ejemplos contribuidos son típicos en cualquier clase fundamental. ¿De qué otra manera se escribiría una pregunta sucinta sobre la precedencia del operador en un examen? Doy clases de matemáticas, y si tuviera un centavo por cada vez que un estudiante le preguntó "¿Cuándo voy a hacer esto en verdadera vida?" ...
a Scott
44
@Scott Hay una idea, y luego hay una idea . Supongamos que eres un instructor de entrenamiento de conductores. Suponga que le pide a su estudiante que maneje a través del tráfico pesado del centro mientras lo golpea en la cabeza con un bate de béisbol. Podría decirse que el estudiante aprenderá una habilidad potencialmente útil. Pero yo llamo a esto abuso. Y mantengo mi opinión de que el código en esta pregunta es abusivo, con un valor pedagógico negativo.
Steve Summit
1
"Respaldo mi opinión" es justo, pero la palabra clave aquí es "opinión". Una respuesta de StackOverflow quizás no sea el lugar óptimo para expresar sus opiniones. :)
Scott
1
Para el registro, he editado esta respuesta bastante significativamente desde que fue aceptada y votada. Sin embargo, todavía dice básicamente las mismas cosas.
Steve Summit
11

A primera vista, parece que este código invoca un comportamiento indefinido, sin embargo, ese no es el caso.

Primero formateemos el código correctamente:

#include<stdio.h>

int main(){
    int k = 0;
    while(+(+k--)!=0)
        k=k++;
    printf("%d\n", k);  
    return 0;
}

Así que ahora podemos ver que la declaración k=k++;está dentro del ciclo.

Ahora rastreemos el programa:

Cuando la condición del bucle se evalúa por primera vez, ktiene el valor 0. La expresión k--tiene el valor actual de k, que es 0, y kse disminuye como un efecto secundario. Entonces, después de esta declaración, el valor de kes -1.

El inicio +de esta expresión no tiene ningún efecto sobre el valor, por lo que se +k--evalúa a 0 y se +(+k--)evalúa de manera similar a 0.

Luego !=se evalúa al operador. Como 0!=0es falso, no se ingresa el cuerpo del bucle . Si se hubiera ingresado el cuerpo, invocaría un comportamiento indefinido porque k=k++tanto las lecturas como las escrituras ksin un punto de secuencia. Pero el ciclo no se ingresa, por lo que no hay UB.

Finalmente kse imprime el valor de que es -1.

dbush
fuente
1
Es una pregunta abierta, existencial, filosófica e imponderable si el comportamiento indefinido que no se ejecuta no es indefinido. ¿No es indefinido? No lo creo.
Steve Summit
2
@SteveSummit No hay absolutamente nada existencial, filosófico, imponderable en absoluto. if (x != NULL) *x = 42;¿Esto es indefinido cuando x == NULL? Por supuesto no. El comportamiento indefinido no ocurre en partes del código que no se ejecutan. La palabra comportamiento es una pista. El código que no se ejecuta no tiene comportamiento, indefinido o no.
n. 'pronombres' m.
1
@SteveSummit Si un comportamiento indefinido que no se ejecuta invalidaría todo el programa, ningún programa en C tendría un comportamiento definido. Debido a que los programas C siempre son solo una condición de bucle / if lejos de ejecutar UB. Tome una iteración de matriz simple como ejemplo: itera hasta que falla la verificación enlazada. Ejecutar la siguiente iteración del bucle sería un comportamiento indefinido, pero como nunca se ejecuta, todo está bien.
cmaster - reinstalar a monica el
3
No voy a discutir sobre eso, porque no soy abogado de idiomas. Pero apuesto a que si hiciera esta pregunta y la etiquetara como abogado de idioma, podría iniciar un debate enérgico. Tenga en cuenta que k=k++es cualitativamente diferente de *x=42. El último está bien definido si xes un puntero válido, pero el primero no está definido, pase lo que pase. (Admito que puede que tengas razón, pero de nuevo, no voy a discutir sobre eso, y me preocupa cada vez más que hayamos sido controlados magistralmente)
Steve Summit, el
1
"El último está bien definido si x es un puntero válido, pero el primero no está definido, pase lo que pase". Solo programas completos pueden tener un comportamiento indefinido. Esta noción no es aplicable a fragmentos de código tomados de forma aislada.
n. 'pronombres' m.
3

Aquí hay una versión de esto que muestra la precedencia del operador:

+(+(k--))

Los dos +operadores unarios no hacen nada, por lo que esta expresión es exactamente equivalente a k--. La persona que escribió esto probablemente estaba tratando de meterse con tu mente.

SS Anne
fuente