Supongamos que tengo una estructura como esta:
volatile struct { int foo; int bar; } data; data.foo = 1; data.bar = 2; data.foo = 3; data.bar = 4;¿Se garantiza que todas las asignaciones no se reordenarán?
Por ejemplo, sin volátil, el compilador claramente podría optimizarlo como dos instrucciones en un orden diferente como este:
data.bar = 4; data.foo = 3;Pero con volatile, ¿se requiere que el compilador no haga algo como esto?
data.foo = 1; data.foo = 3; data.bar = 2; data.bar = 4;(Tratar a los miembros como entidades volátiles separadas y no relacionadas, y hacer un reordenamiento que me imagino que podría intentar mejorar la localidad de referencia en caso de que foo y bar estén en un límite de página, por ejemplo).
Además, ¿la respuesta es consistente para las versiones actuales de los estándares C y C++?
No se reordenarán.
C17 6.5.2.3(3) dice:
Una expresión postfija seguida por el . operador y un identificador designa un miembro de una estructura u objeto de unión. El valor es el del miembro nombrado, 97) y es un lvalue si la primera expresión es un lvalue. Si la primera expresión tiene un tipo calificado, el resultado tiene la versión calificada del tipo del miembro designado.
Dado data tienen un tipo calificado como volatile , también lo tienen data.bar y data.foo . Por lo tanto, está realizando dos asignaciones a objetos volatile int . Y por 6.7.3 nota al pie 136,
Las acciones sobre los objetos así declarados [como
volatile] no serán "optimizados" por una implementación ni reordenados excepto según lo permitido por las reglas para evaluar expresiones.
Una pregunta más sutil es si el compilador podría asignarlos a ambos con una sola instrucción, por ejemplo, si son valores contiguos de 32 bits, ¿podría usar un almacén de 64 bits para configurar ambos? Creo que no, y al menos GCC y Clang no lo intentan.
Si desea usar esto en varios subprocesos, hay un problema importante.
Si bien el compilador no reordenará las escrituras en variables volatile (como se describe en la respuesta de Nate Eldredge ), hay un punto más donde puede ocurrir el reordenamiento de escritura, y ese es la propia CPU. Esto depende de la arquitectura de la CPU, y a continuación se muestran algunos ejemplos:
Consulte el documento técnico de solicitud de memoria de la arquitectura Intel® 64 .
Si bien las instrucciones de la tienda en sí no se reordenan (2.2):
- Las tiendas no se reordenan con otras tiendas.
Pueden ser visibles para diferentes CPU en un orden diferente (2.4):
El orden de la memoria Intel 64 permite que las tiendas de dos procesadores se vean en diferentes órdenes por esos dos procesadores
AMD 64 (que es el x64 común) tiene un comportamiento similar en la especificación :
Por lo general, no se permiten escrituras desordenadas. Las instrucciones de escritura ejecutadas fuera de orden no pueden consignar (escribir) su resultado en la memoria hasta que todas las instrucciones anteriores se hayan completado en el orden del programa. El procesador puede, sin embargo, mantener el resultado de una instrucción de escritura fuera de orden en un búfer privado (no visible para el software) hasta que ese resultado pueda guardarse en la memoria.
Recuerdo que tuve que tener cuidado con esto en Xbox 360 que usaba una CPU PowerPC :
Si bien la CPU de Xbox 360 no reordena las instrucciones, reorganiza las operaciones de escritura, que se completan después de las propias instrucciones. Esta reorganización de escrituras está específicamente permitida por el modelo de memoria PowerPC
Para evitar el reordenamiento de la CPU de forma portátil, debe usarvallas de memoria como C++11 std::atomic_thread_fence o C11 atomic_thread_fence . Sin ellos, el orden de escritura visto desde otro subproceso puede ser diferente.
Véase también C++11 introdujo un modelo de memoria estandarizado. ¿Qué significa? ¿Y cómo afectará a la programación en C++?
Esto también se observa en el artículo de la barrera de memoria de Wikipedia:
Además, no se garantiza que las lecturas y escrituras volátiles se vean en el mismo orden por otros procesadores o núcleos debido al almacenamiento en caché, el protocolo de coherencia de caché y el orden relajado de la memoria, lo que significa que las variables volátiles por sí solas pueden ni siquiera funcionar como indicadores entre subprocesos o mutexes. .