hacer - detectar un cortocircuito en el cableado
¿El estándar C actual prohíbe el cortocircuito `&` y `|`? (4)
Me parece razonable tomar el hecho de que 6.5.10 no define un punto de secuencia para significar que siempre se llamará a foo y una implementación que no lo llamó no sería estándar. ¿Tengo razón sobre eso?
Si y no. De hecho, la implementación que no llamaría foo no sería estándar. Sin embargo, no tiene nada que ver con los puntos de secuencia.
El párrafo que se aplicaría aquí sería 5.1.2.3/3:
En la máquina abstracta, todas las expresiones son evaluadas como especificadas 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 los efectos secundarios necesarios (incluidos los causados por llamar a una función o acceder a un objeto volátil).
¿Hay algo en el estándar C (supongo que en este momento es C99 + TC1-3 C11 ) que garantiza que &
y |
no será cortocircuitado?
Si escribo:
x = y & foo();
... Espero que siempre se llame a foo
, pero ¿está realmente definido? En teoría, a menos que el estándar diga lo contrario, si y
contiene 0
, una optimización de tiempo de ejecución podría omitir la llamada en ausencia de algo que diga que no está permitido. (Y de manera similar con |
, podría ignorar el operando de la derecha si el operando de la izquierda ya estuviera todo activado. De hecho, incluso x = y * foo();
podría estar en cortocircuito si y
fuera 0
.)
No conocer bien la especificación (y yo no), es difícil demostrar un negativo como ese. Puedo contrastar las secciones en &
(6.5.10 en C99) y &&
(6.5.13 en C99). En este último, queda perfectamente claro:
A diferencia del operador
&
binario bitwise, el operador&&
garantiza la evaluación de izquierda a derecha; Hay un punto de secuencia después de la evaluación del primer operando. Si el primer operando se compara igual a0
, el segundo operando no se evalúa.
... pero 6.5.10 no indica específicamente la versión negativa de eso.
Me parece razonable tomar el hecho de que 6.5.10 no define un punto de secuencia para significar que siempre se llamará a foo
y una implementación que no lo llamó no sería estándar. ¿Tengo razón sobre eso?
§6.5.10 Operador bit a bit Y
...
Semántica
3 Las conversiones aritméticas habituales se realizan en los operandos.
Ahí tienes. La aplicación de las conversiones aritméticas habituales requiere que se evalúen ambos operandos. Tenga en cuenta que este párrafo no está presente en la descripción de la semántica de &&
, ||
, o ?:
.
En este caso, los puntos de secuencia no tienen nada que ver con eso. El estándar C define la forma en que se comporta la máquina lógica, y requiere que una implementación se comporte como si siguiera ese comportamiento; Cualquier optimización debe ser transparente con respecto al comportamiento del programa.
Los únicos operadores en el lenguaje C que están definidos para no evaluar (todos) sus operandos son ?:
, &&
, ||
, y sizeof
. La única forma en que una implementación podría cortocircuitar |
o &
es si determinó (1) que el valor de un solo operando es suficiente para conocer el resultado, o al menos para saber si el resultado es cero o distinto de cero cuando el resultado solo se usa como un valor de verdad, y (2 ) que el otro operando no tiene efectos secundarios, y por lo tanto el comportamiento de no evaluarlo es el mismo que si fuera evaluado.
Con una llamada de función, es poco probable que el compilador pueda determinar que no tiene efectos secundarios a menos que sea static
o marcado con un atributo específico del compilador como __attribute__((const))
gcc __attribute__((const))
.
Edición: Desde C99, 5.1.2.3:
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 los efectos secundarios necesarios (incluidos los causados por llamar a una función o acceder a un objeto volátil).
Los operadores que no evalúan sus operandos se documentan explícitamente como tales.
Si la implementación puede determinar que la llamada a la función foo
no cambia el nivel observable del programa, no es necesario evaluar la llamada foo
.
(C11, 5.1.2.3p4) "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 necesitan efectos secundarios. se producen (incluidos los causados por llamar a una función o acceder a un objeto volátil) ".
La noción de comportamiento observable se agrega y aclara en C11:
(C11, 5.1.2.3p6) "Los requisitos mínimos para una implementación conforme son: - Los accesos a objetos volátiles se evalúan estrictamente de acuerdo con las reglas de la máquina abstracta. - Al finalizar el programa, todos los datos escritos en archivos deben ser idénticos a los como resultado de la ejecución de un programa de acuerdo con la semántica abstracta, la dinámica de entrada y salida de los dispositivos interactivos se llevará a cabo como se especifica en 7.21.3. La intención de estos requisitos es que la salida sin buffer o con búfer de línea aparezca tan pronto como sea posible, para asegurarse de que los mensajes de solicitud realmente aparezcan antes de que un programa esté en espera de entrada. Este es el comportamiento observable del programa ".