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 ª:
int sc_and(int a, int b) {
return a ? b : 0;
}
Me parece que en el caso de que a
sea falso, el programa no intentará evaluar b
en absoluto, pero debo estar equivocado. ¿Por qué el programa incluso toca b
en este caso, cuando no es necesario?
c
short-circuiting
Bobazonski
fuente
fuente
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.IIf(x Is Nothing, Default, x.SomeValue)
.Respuestas:
Esta es una pregunta con trampa.
b
es un argumento de entrada para elsc_and
método, por lo que siempre se evaluará. En otras palabrassc_and(a(), b())
, llamaráa()
y llamaráb()
(orden no garantizada), luego llamarásc_and
con los resultados de loa(), b()
que pasa aa?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:
int sc_and(int a, int b){ return a?b:0; } int a(){ cout<<"called a!"<<endl; return 0; } int b(){ cout<<"called b!"<<endl; return 1; } int main(char* argc, char** argv){ int x = sc_and(a(), b()); return 0; }
Inmediatamente quedaría claro que se supone que debe pensar
sc_and
como un operador en sí mismo en su propio lenguaje específico de dominio , y evaluar si la llamada asc_and
exhibe 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 unsc_and
cortocircuito, lo que implicaría usar una en#define
lugar 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".
fuente
?
, como enif (condition) {when true} else {when-false}
. Eso no se llama cortocircuito.x Sand y
(usando Sand para denotar la variedad de cortocircuito) es equivalente a la expresión condicional aif x then y else false;
la quex Sor y
es equivalente la expresiónif x then true else y
. .if else
, no unif
. E incluso entonces, no lo es realmente; el?:
operador es una expresión ,if-else
es 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 (&&, ||
).Cuando la declaración
bool x = a && b++; // a and b are of int type
se ejecuta,
b++
no se evaluará si el operandoa
evaluado comofalse
(comportamiento de cortocircuito). Esto significa queb
no se producirá el efecto secundario .Ahora, mira la función:
bool and_fun(int a, int b) { return a && b; }
y llama a esto
bool x = and_fun(a, b++);
En este caso, si
a
estrue
ofalse
,b++
siempre se evaluará 1 durante la llamada a la función yb
siempre se producirá el efecto secundario .Lo mismo es cierto para
int x = a ? b : 0; // Short circuit behavior
y
int sc_and (int a, int b) // No short circuit behavior. { return a ? b : 0; }
1 El orden de evaluación de los argumentos de la función no está especificado.
fuente
int x = a ? b++ : 0
, como un cortocircuito observable.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
#define sc_and(a, b) \ ((a) ?(b) :0)
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.
fuente
Editado para corregir los errores anotados en el comentario de @cmasters.
En
int sc_and(int a, int b) { return a ? b : 0; }
... la
return
expresión ed muestra una evaluación de cortocircuito, pero la llamada a la función no.Intenta llamar
sc_and (0, 1 / 0);
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:
y
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:
1 / 0
produce un comportamiento indefinido?PD
Incluso si cambia a C ++ y define
sc_and
como unainline
función, no obtendrá SCE. Si lo define como una macro de C, como lo hace @alk , ciertamente lo hará.fuente
inline
funció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 ambosf()
yg()
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, inclusosc_and()
...inline
conservaba 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.Para ver claramente el cortocircuito de operaciones ternarias, intente cambiar ligeramente el código para usar punteros de función en lugar de enteros:
int a() { printf("I'm a() returning 0\n"); return 0; } int b() { printf("And I'm b() returning 1 (not that it matters)\n"); return 1; } int sc_and(int (*a)(), int (*b)()) { a() ? b() : 0; } int main() { sc_and(a, b); return 0; }
Y luego compílelo (incluso con casi NINGUNA optimización
-O0
:!). Verá queb()
no se ejecuta sia()
devuelve falso.% gcc -O0 tershort.c % ./a.out I'm a() returning 0 %
Aquí el ensamblaje generado se ve así:
call *%rdx <-- call a() testl %eax, %eax <-- test result je .L8 <-- skip if 0 (false) movq -16(%rbp), %rdx movl $0, %eax call *%rdx <- calls b() only if not skipped .L8:
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.
fuente
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:
int ret = a ? b : c; // Here, b and c are expressions that return a value.
Es casi equivalente al siguiente código:
int ret; if(a) {ret = b} else {ret = c}
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.
fuente
x = a ? b : 0;
es semánticamente lo mismo que(a && (x = b)) || (x = 0);