¿Por qué esta versión de AND lógico en C no muestra un comportamiento de cortocircuito?


80

Sí, esta es una pregunta para hacer en casa, pero he investigado mucho y he reflexionado bastante sobre el tema y no puedo resolverlo. La pregunta establece que esta pieza de código NO exhibe un comportamiento de cortocircuito y pregunta por qué. Pero me parece que exhibe un comportamiento de cortocircuito, entonces, ¿alguien puede explicar por qué no lo hace?

C ª:

Me parece que en el caso de que asea ​​falso, el programa no intentará evaluar ben absoluto, pero debo estar equivocado. ¿Por qué el programa incluso toca ben este caso, cuando no es necesario?


11
Como es típico en la mayoría de las preguntas de tarea artificial, nunca verá este tipo de código en un sistema de producción, a menos que el programador intente deliberadamente ser inteligente.
Robert Harvey

49
@RobertHarvey ¡Verá código exactamente como este en los sistemas de producción todo el tiempo! No es probable que sea una función nombrada AND(), pero las funciones que reciben argumentos por valor y luego los evalúan (o no) dependiendo de la lógica de la función están en todas partes. A pesar de ser una "pregunta capciosa", es un comportamiento crítico de C de entender. En un idioma con llamada por nombre, esta pregunta tendría una respuesta muy diferente.
Ben Jackson

7
@BenJackson: Estaba comentando el código, no el comportamiento. Sí, necesita comprender el comportamiento. No, no es necesario que escriba un código como este.
Robert Harvey

3
Esto es bastante relevante si alguna vez tiene que codificar en VB y encontrar IIf. Debido a que es una función más que un operador, la evaluación no está en cortocircuito. Esto puede crear problemas para los desarrolladores acostumbrados al operador de cortocircuito que luego escribe cosas como IIf(x Is Nothing, Default, x.SomeValue).
Dan Bryant

3
@alk porque es una acusación contra el sistema educativo.
Shivan Dragon

Respuestas:


118

Esta es una pregunta con trampa. bes un argumento de entrada para el sc_andmétodo, por lo que siempre se evaluará. En otras palabras sc_and(a(), b()), llamará a()y llamará b()(orden no garantizada), luego llamará sc_andcon los resultados de lo a(), b()que pasa a a?b:0. No tiene nada que ver con el propio operador ternario, que provocaría un cortocircuito absoluto.

ACTUALIZAR

Con respecto a por qué llamé a esto una 'pregunta con trampa': se debe a la falta de un contexto bien definido sobre dónde considerar 'cortocircuito' (al menos como lo reproduce el OP). Muchas personas, cuando se les da solo una definición de función, asumen que el contexto de la pregunta es sobre el cuerpo de la función ; a menudo no consideran la función como una expresión en sí misma. Este es el "truco" de la pregunta; Para recordarle que en la programación en general, pero especialmente en lenguajes como C-likes que a menudo tienen muchas excepciones a las reglas, no puede hacer eso. Ejemplo, si la pregunta se hizo como tal:

Considere el siguiente código. Sc_and mostrará un comportamiento de cortocircuito cuando se llame desde main :

Inmediatamente quedaría claro que se supone que debe pensar sc_andcomo un operador en sí mismo en su propio lenguaje específico de dominio , y evaluar si la llamada a sc_andexhibe un comportamiento de cortocircuito como lo &&haría un usuario regular . No consideraría que sea una pregunta capciosa en absoluto, porque está claro que no se supone que debe enfocarse en el operador ternario y, en cambio, se supone que debe enfocarse en la mecánica de llamada de función de C / C ++ (y, supongo, dirigir muy bien en una pregunta de seguimiento para escribir un sc_andcortocircuito, lo que implicaría usar una en #definelugar de una función).

El hecho de que llame o no a lo que el propio operador ternario hace cortocircuito (o algo más, como "evaluación condicional") depende de su definición de cortocircuito, y puede leer los diversos comentarios para reflexionar sobre eso. Por lo mío lo hace, pero no es muy relevante para la pregunta real o por qué lo llamé un "truco".


13
¿El operador ternario hace cortocircuitos? No. Evalúa una de las expresiones que siguen a ?, como en if (condition) {when true} else {when-false}. Eso no se llama cortocircuito.
Jens


17
@Jens: Yo llamaría a cualquier caso en el que un operador omita la evaluación de uno o más de sus operandos una forma de "cortocircuito", pero esto es realmente una cuestión de cómo eliges definir el término.
R .. GitHub DEJA DE AYUDAR A ICE

13
@Jens Es azúcar sintáctico para un if else, no un if. E incluso entonces, no lo es realmente; el ?:operador es una expresión , if-elsees una declaración . Esas son cosas semánticamente muy diferentes, incluso si puede construir un conjunto equivalente de declaraciones que produzcan el mismo efecto que la expresión ternaria . Por eso se considera un cortocircuito; la mayoría de las otras expresiones siempre evalúan todos sus operandos ( +,-,*,/etc.). Cuando no lo hacen, es un cortocircuito ( &&, ||).
aruisdante

9
Sí, para mí la distinción importante es que estamos hablando de operadores. Por supuesto , las declaraciones de control de flujo controlan qué ruta de código se ejecuta. Para los operadores, no es obvio para alguien que no esté familiarizado con los lenguajes C y derivados de C que un operador podría no evaluar todos sus operandos, por lo que es importante tener un término para hablar sobre esta propiedad, y para eso, uso "short en circuito ".
R .. GitHub DEJA DE AYUDAR A ICE

43

Cuando la declaración

se ejecuta, b++no se evaluará si el operando aevaluado como false(comportamiento de cortocircuito). Esto significa que bno se producirá el efecto secundario .

Ahora, mira la función:

y llama a esto

En este caso, si aes trueo false, b++siempre se evaluará 1 durante la llamada a la función y bsiempre se producirá el efecto secundario .

Lo mismo es cierto para

y


1 El orden de evaluación de los argumentos de la función no está especificado.


1
OK, coroborando con la respuesta de Stephen Quan: ¿es posible (legal) que un compilar pueda incorporar la función "and_fun", de modo que cuando llame a "bool x = and_fun (a, b ++);" b ++ no se incrementará si a es verdadero?
Shivan Dragon

4
@ShivanDragon Eso me suena a cambiar el comportamiento observable.
sapi

3
@ShivanDragon: cuando se inserta, el compilador no cambiará el comportamiento. No es tan simple como sustituir el cuerpo de la función en.
Jack Aidley

Para mayor claridad, podría agregar int x = a ? b++ : 0, como un cortocircuito observable.
Paul Draper

@PaulDraper; Conservé el fragmento original tal como está para que los lectores no lo confundan.
haccks

19

Como ya han señalado otros, no importa qué se pase a la función como los dos argumentos, se evaluará a medida que se pase. Eso es mucho antes de la operación tenary.

Por otro lado, este

haría "cortocircuito", ya que esta macro no implica una llamada de función y con esto no se realiza ninguna evaluación de los argumentos de una función.


tal vez explicar por qué? Me parece exactamente igual que el fragmento de la operación. Excepto que está en línea en lugar de un alcance diferente.
dhein

5

Editado para corregir los errores anotados en el comentario de @cmasters.


En

... la returnexpresión ed muestra una evaluación de cortocircuito, pero la llamada a la función no.

Intenta llamar

La llamada a la función se evalúa 1 / 0, aunque nunca se utiliza, por lo que provoca, probablemente, un error de división por cero.

Los extractos relevantes del (borrador) de la Norma ANSI C son:

2.1.2.3 Ejecución del programa

...

En la máquina abstracta, todas las expresiones se evalúan según lo especificado por la semántica. Una implementación real no necesita evaluar parte de una expresión si puede deducir que su valor no se usa y que no se producen efectos secundarios necesarios (incluidos los causados ​​por llamar a una función o acceder a un objeto volátil).

y

3.3.2.2 Llamadas a funciones

....

Semántica

...

Al prepararse para la llamada a una función, se evalúan los argumentos y a cada parámetro se le asigna el valor del argumento correspondiente.

Supongo que cada argumento se evalúa como una expresión, pero que la lista de argumentos en su conjunto no es una expresión, por lo que el comportamiento no SCE es obligatorio.

Como salpicón en la superficie de las aguas profundas del estándar C, agradecería una vista debidamente informada sobre dos aspectos:

  • ¿Evaluar 1 / 0produce un comportamiento indefinido?
  • ¿Es una lista de argumentos una expresión? (Yo creo que no)

PD

Incluso si cambia a C ++ y define sc_andcomo una inlinefunción, no obtendrá SCE. Si lo define como una macro de C, como lo hace @alk , ciertamente lo hará.


1
No, una inlinefunción no cambia la semántica de una llamada a función. Y esa semántica especifica que todos los argumentos se evalúan, como citó correctamente. Incluso si el compilador puede optimizar la llamada, el comportamiento visible no debe cambiar, es decir, sc_and(f(), g())debe comportarse como si ambos f()y g()siempre fueran llamados. sc_and(0, 1/0)es un mal ejemplo, ya que es un comportamiento indefinido, y el compilador no está obligado a llamada, incluso sc_and()...
cmaster - Restablecer Monica

@cmaster Gracias. Simplemente no sabía que C ++ inlineconservaba la semántica de llamadas. Me quedé con la división cero, como se ajusta al ejemplo, y creo que SCE a menudo se explota para evitar un comportamiento indefinido.
Miniatura del

4

Para ver claramente el cortocircuito de operaciones ternarias, intente cambiar ligeramente el código para usar punteros de función en lugar de enteros:

Y luego compílelo (incluso con casi NINGUNA optimización -O0:!). Verá que b()no se ejecuta si a()devuelve falso.

Aquí el ensamblaje generado se ve así:

Entonces, como otros señalaron correctamente, el truco de la pregunta es hacer que se concentre en el comportamiento del operador ternario que NO cortocircuita (llame a eso 'evaluación condicional') en lugar de la evaluación de parámetros en la llamada (llamada por valor) que NO cortocircuita.


0

El operador C ternario nunca puede cortocircuito, ya que sólo se evalúa una única expresión de una (la condición), para determinar un valor dado por las expresiones b y c , si podría ser devuelto cualquier valor.

El siguiente código:

Es casi equivalente al siguiente código:

La expresión a puede estar formada por otros operadores como && o || que pueden cortocircuitar porque pueden evaluar dos expresiones antes de devolver un valor, pero eso no se consideraría como el operador ternario haciendo cortocircuito, sino como los operadores usados ​​en la condición como lo hace en una declaración if regular.

Actualizar:

Existe cierto debate acerca de que el operador ternario es un operador de cortocircuito. El argumento dice que cualquier operador que no evalúe todos sus operandos hace un cortocircuito de acuerdo con @aruisdante en el comentario a continuación. Si se le da esta definición, entonces el operador ternario estaría en cortocircuito y en el caso de que esta sea la definición original, estoy de acuerdo. El problema es que el término "cortocircuito" se usó originalmente para un tipo específico de operador que permitía este comportamiento y esos son los operadores lógicos / booleanos, y la razón por la que son solo esos es lo que intentaré explicar.

Siguiendo el artículo Evaluación de cortocircuito , la evaluación de cortocircuito solo se refiere a operadores booleanos implementados en el lenguaje de una manera en la que saber que el primer operando hará que el segundo sea irrelevante, es decir, para el operador && siendo el primer operando falso , y para el || siendo el operador verdadero el primer operando , la especificación C11 también lo indica en 6.5.13 Operador lógico AND y 6.5.14 Operador lógico OR.

Esto significa que para que se identifique el comportamiento de cortocircuito, esperaría identificarlo en un operador que debe evaluar todos los operandos al igual que los operadores booleanos si el primer operando no hace irrelevante al segundo. Esto está en línea con lo que está escrito en otra definición de cortocircuito en MathWorks en la sección "Cortocircuito lógico", ya que el cortocircuito proviene de los operadores lógicos.

Como he estado tratando de explicar el operador ternario de C, también llamado ternario si, solo evalúa dos de los operandos, evalúa el primero y luego evalúa un segundo, cualquiera de los dos restantes depende del valor del el primero. Siempre hace esto, no se supone que esté evaluando los tres en ninguna situación, por lo que no hay "cortocircuito" en ningún caso.

Como siempre, si ve que algo no está bien, por favor escriba un comentario con un argumento en contra y no solo un voto negativo, eso solo empeora la experiencia de SO, y creo que podemos ser una comunidad mucho mejor que una que solo vota negativamente las respuestas. uno no está de acuerdo.


3
¿Y por qué el voto negativo? Me gustaría recibir comentarios para poder corregir lo que dije mal. Sea constructivo.
Adrián Pérez

2
Quizás -1 porque no está 100% en tema. pero de todos modos di +1 porque su respuesta al menos lo explica en estado de estado y no parece estar mal ompv.
dhein

4
@AdrianPerez vea la sección de comentarios en mi respuesta para saber por qué la mayoría de las personas considerarían al 100% que el operador ternario es un circuito corto. Es una expresión que no evalúa todos sus operandos en función del valor de algunos de sus operandos. Eso es un cortocircuito.
aruisdante

3
No puedo pensar en ningún operador no booleano que no evalúe todos sus operandos. Pero eso es aparte del punto; la razón por la que se llama cortocircuito es precisamente porque es un operador (o en el sentido general, una expresión en lugar de una declaración) que no evalúa todos sus operandos. El tipo de operador realmente no importa. Sería perfectamente posible escribir operadores booleanos que no produjeran un cortocircuito, y podría escribir operadores no booleanos que también lo hicieran (por ejemplo, la multiplicación de enteros de 0 siempre es 0, por lo que devuelve 0 inmediatamente si el primer operando es 0).
aruisdante

2
x = a ? b : 0;es semánticamente lo mismo que(a && (x = b)) || (x = 0);
jxh
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.