Sucedo que profundizo en C y bloqueo la programación libre. Jugando con eso, me pregunto qué garantía puede darme gcc de que un programa se ejecuta exactamente de la misma manera que lo escribo y no se realiza ninguna optimización de registro en un determinado paso y no se cambia ninguna operación en su orden.
Tal como lo entiendo actualmente, garantiza que las operaciones de memoria ocurran en el mismo orden y que las llamadas a métodos ocurran en el mismo orden junto con ellas mismas y las operaciones de memoria. Entre reordenación podría ocurrir.
La optimización del registro se puede desactivar utilizando la palabra clave volátil.
¿Hay garantías adicionales o casos de esquina que C y especialmente gcc ha implicado?
Solo se requiere un compilador para no cambiar el significado de un programa.
El significado de un programa está dado por la semántica de sus sentencias C, por ejemplo el calificativo volatile es una forma de reificar a nivel semántico la interacción con agentes externos.
volatile solo, sin embargo, es inútil cuando se trata de la sincronización de subprocesos, solo tiene efectos locales .
Entonces, solo debe implicar lo que implica el estándar de C, si el estándar no le da semántica de orden a una declaración ni ningún efecto secundario, entonces no hay ninguno.
Para optimizar algún código, el compilador debe demostrar que la optimización no cambia el significado.
En general, este es un problema difícil (o incluso indecidible), por lo que se hace solo en un contexto simple.
Considerar
#include <stdio.h> int simple(const int a, const int b) { int c = a + b; //3x Memory operation? int d = c*c; //2x Memory operation? return d+d; //Memory operation? } int main() { int a = 0; //Memory operation? int b = 0; //Memory operation? a = simple(2, 3); //Function call + Memory operation? b = simple(3, 4); //Function call + Memory operation? printf("%d %d\n", a, b); //Function call + 3x Memory operation? return 0; } El estándar C dicta que a = simple(2, 3); se ejecuta antes de b = simple(3, 4); como el final de una expresión es un punto de secuencia.
Este es el código generado por gcc con optimización completa
lea 0x18f0(%rip),%rcx # 0x100403030, "%d %d\n" mov $0x62,%r8d mov $0x32,%edx callq 0x100401110 <printf>Usé cygwin, por lo que el ABI es el de Windows. Esto es equivalente a
printf("%d %d\n", 50, 98); Este es un ejemplo ad hoc, la función simple es pura y toma expresiones constantes de tiempo de compilación , por lo que el resultado se conoce en tiempo de compilación.
Esta es la prueba de que gcc necesitaba optimizar las llamadas .
Al escribir código sin bloqueo, no debe preocuparse por las optimizaciones del compilador siempre que use la semántica correcta (por ejemplo, volatile por tener accesos de lectura y escritura como efectos secundarios solo por el bien de la optimización ).
Lo que realmente debería preocuparte es el orden de la memoria como se señala en mi comentario.
C11 finalmente materializa todo esto en su modelo de memoria .
Si su código depende de no reordenar las operaciones y no hace ninguna otra optimización, es simplemente un "comportamiento indefinido".
Si busca tales garantías, simplemente busca una garantía de que escribe un programa roto que se ejecuta correctamente. Debe usar la semántica que está representada por su idioma usado. Si necesita suposiciones sobre cómo el lenguaje implementa las acciones en un sistema operativo y una máquina determinados, ¡está totalmente en el camino equivocado!
Si realmente tiene algún tipo de garantía para una versión real del compilador y un sistema operativo subyacente usado, ¡esto no estará garantizado en la función! Entonces, la expresión "garantía" tampoco es realmente cierta. Si es necesario, la respuesta simple es: ¡No hay garantía en absoluto! El lenguaje tiene una semántica y el compilador garantiza implementarla. ¡No más en absoluto!.
Las garantías de pedido entre subprocesos en C solo se pueden lograr con stdatomic.h de C11 o extensiones específicas del compilador. En todos los demás casos, lo único que el compilador debe garantizar es que el comportamiento visible externamente del programa (esto podría traducirse aproximadamente como: llamadas a funciones y referencias a la memoria que no están bajo el control del compilador) es el mismo que se interpreta de acuerdo con el estándar. Antes de que los subprocesos de C11 no existieran desde el punto de vista del estándar C, por lo que no se preocupaba por el comportamiento de los subprocesos.