relacionales - ¿Por qué esta versión de AND lógico en C no muestra un comportamiento de cortocircuito?
operadores relacionales en c++ (6)
Sí, esta es una pregunta para la tarea, pero hice mi investigación y una buena cantidad de pensamiento profundo sobre el tema y no puedo resolver esto. La pregunta establece que este fragmento de código NO muestra un comportamiento de cortocircuito y pregunta por qué. Pero me parece que muestra un comportamiento de cortocircuito, por lo que 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?
Como ya se ha señalado por otros, no importa qué pasa a la función como los dos argumentos, se evalúa a medida que se pasa. Eso es mucho antes de la operación del tenary.
Por otro lado, esto
#define sc_and(a, b) /
((a) ?(b) :0)
serí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.
Cuando la declaración
bool x = a && b++; // a and b are of int type
ejecuta, b++
no se evaluará si el operando a
evalúa como false
(comportamiento de cortocircuito). Esto significa que el efecto secundario sobre b
no tendrá lugar.
Ahora, mira la función:
bool and_fun(int a, int b)
{
return a && b;
}
y llama esto
bool x = and_fun(a, b++);
En este caso, si a
es true
o false
, b++
siempre se evaluará durante la llamada a la función y el efecto secundario sobre b
siempre tendrá lugar.
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;
}
Editado para corregir los errores anotados en el comentario de @cmasters.
En
int sc_and(int a, int b) {
return a ? b : 0;
}
... la expresión de return
ed muestra una evaluación de cortocircuito, pero la llamada a función no.
Intenta llamar
sc_and (0, 1 / 0);
La llamada de función evalúa 1 / 0
, aunque nunca se usa, lo que provoca, probablemente, un error de división por cero.
Los extractos relevantes del (borrador) del Estándar 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 de función
....
Semántica
...
Al prepararse para la llamada a una función, los argumentos son evaluados, 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 como un todo no es una expresión, por lo tanto, el comportamiento no SCE es obligatorio.
Como una salpicadura en la superficie de las aguas profundas del estándar C, agradecería una visión adecuadamente informada sobre dos aspectos:
- ¿La evaluación
1 / 0
1/0 produce un comportamiento indefinido? - ¿Es una lista de argumentos una expresión? (Yo creo que no)
PD
Incluso si te mueves a C ++ y defines sc_and
como una función en inline
, no obtendrás SCE. Si lo defines como una macro C, como @alk hace @alk , ciertamente lo harás.
El operador C ternario nunca puede cortocircuitarse, porque solo evalúa una única expresión a (la condición), para determinar un valor dado por las expresiones byc , si se puede devolver 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 || eso puede cortocircuitarse porque pueden evaluar dos expresiones antes de devolver un valor, pero eso no se consideraría como el operador ternario que hace cortocircuito pero los operadores lo usan en la condición como lo hace en una instrucció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 se cortocircuita de acuerdo con @aruisdante en el siguiente comentario. 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 cual son solo eso 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 manera que saber que el primer operando hará que el segundo sea irrelevante, esto es, para el operador && es el primer operando falso y para el || siendo el operador el primer operando verdadero , la especificación C11 también lo anota en 6.5.13 operador lógico AND y 6.5.14 operador lógico OR.
Esto significa que para identificar el comportamiento del cortocircuito, esperaría identificarlo en un operador que debe evaluar todos los operandos como los operadores booleanos si el primer operando no hace irrelevante el segundo. Esto está en línea con lo que está escrito en otra definición para el 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 al operador ternario, también llamado ternario si, solo evalúa dos de los operandos, evalúa el primero y luego evalúa el segundo, cualquiera de los dos restantes dependiendo del valor del el primero. Siempre hace esto, no se supone que evalúe los tres en ninguna situación, por lo que no hay ningún "cortocircuito" en ningún caso.
Como siempre, si ve algo que no está bien, por favor escriba un comentario con un argumento en contra de esto y no solo un voto negativo, que solo empeorará la experiencia de SO, y creo que podemos ser una comunidad mucho mejor que la respuesta negativa. uno no está de acuerdo con.
Esta es una pregunta con trampa. b
es un argumento de entrada para el método sc_and
, por lo que siempre será evaluado. En otras palabras sc_and(a(), b())
llamará a()
y call b()
(orden no garantizada), luego llama a sc_and
con los resultados de a(), b()
que pasa a a?b:0
. No tiene nada que ver con el operador ternario en sí, lo cual sería un cortocircuito absoluto.
ACTUALIZAR
Con respecto a por qué llamé a esto una "pregunta capciosa": es por la falta de un contexto bien definido para considerar el "cortocircuito" (al menos tal como lo reproduce el PO). Muchas personas, cuando se les da solo una definición de función, suponen que el contexto de la pregunta es preguntar 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 fue hecha como tal:
Considera el siguiente código. Will sc_and exibir comportamiento de cortocircuito cuando se llama desde main :
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;
}
Sería inmediatamente claro que se supone que debes pensar en sc_and
como operador en sí mismo en tu propio lenguaje de dominio específico , y evaluar si la llamada a sc_and
exhibe un comportamiento de cortocircuito como un &&
normal . No lo consideraría una pregunta capciosa, porque está claro que se supone que no debe centrarse en el operador ternario, y se supone que debe centrarse en la mecánica de llamada a función de C / C ++ (y, supongo, conduciría muy bien en una pregunta de seguimiento para escribir un sc_and
que produce un cortocircuito, lo que implicaría usar un #define
lugar de una función).
Si llama o no lo que hace el propio operador ternario, el cortocircuito (u otra cosa, como la "evaluación condicional") depende de su definición de cortocircuito, y puede leer los diversos comentarios para obtener ideas al respecto. Por la mía, sí, pero no es terriblemente relevante para la pregunta real o por qué lo llamé un "truco".
Para ver claramente los cortocircuitos op ternarios, intente cambiar ligeramente el código para usar punteros de función en lugar de números 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 compilarlo (incluso con casi ninguna optimización: -O0
!). Verá que b()
no se ejecuta si a()
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 te enfoques en el comportamiento del operador ternario que HACE cortocircuito (llama a eso ''evaluación condicional'') en lugar de la evaluación de parámetros en llamada (llamada por valor) que NO cortocircuita.