Pregunta ¿La palabra clave volátil de C ++ introduce una valla de memoria?


Entiendo que volatile informa al compilador que el valor puede ser cambiado, pero para lograr esta funcionalidad, ¿el compilador necesita introducir una valla de memoria para que funcione?

Según entiendo, la secuencia de operaciones en objetos volátiles no puede reordenarse y debe conservarse. Esto parece implicar que algunas vallas de memoria son necesarias y que no hay realmente una forma de evitar esto. ¿Estoy en lo correcto al decir esto?


Hay una discusión interesante en esta pregunta relacionada

Jonathan Wakely escribe:

... Los accesos a distintas variables volátiles no pueden ser reordenados por el   compilador, siempre y cuando se produzcan en expresiones completas separadas ... derecha   ese volátil es inútil para la seguridad del hilo, pero no por las razones   da. No es porque el compilador pueda reordenar accesos a   objetos volátiles, pero debido a que la CPU podría reordenarlos. Atómico   las operaciones y las barreras de memoria impiden que el compilador y la CPU   reordenando

A la que David Schwartz respuestas en los comentarios:

... No hay diferencia, desde el punto de vista del estándar C ++,   entre el compilador haciendo algo y el compilador que emite   instrucciones que hacen que el hardware haga algo. Si la CPU puede   reordenar accesos a volátiles, entonces el estándar no requiere eso   su orden será preservada. ...

... El estándar C ++ no hace ninguna distinción sobre qué hace el   reordenando Y no se puede argumentar que la CPU puede reordenarlos sin   efecto observable, así que está bien - el estándar C ++ define su   orden como observable Un compilador cumple con el estándar C ++ en   una plataforma si genera código que hace que la plataforma haga lo que   el estándar requiere Si el estándar requiere accesos a volátiles no   ser reordenado, entonces una plataforma que los reordena no cumple. ...

Mi punto es que si el estándar de C ++ prohíbe al compilador   reordenando los accesos a volátiles distintos, en la teoría de que   el orden de tales accesos es parte del comportamiento observable del programa,   luego también requiere que el compilador emita código que prohíbe la CPU   de hacerlo. El estándar no diferencia entre lo que   el compilador hace y lo que el código de generación del compilador hace la CPU.

Lo cual arroja dos preguntas: ¿Alguno de ellos es "correcto"? ¿Qué hacen realmente las implementaciones reales?


75
2017-10-10 19:51


origen


Respuestas:


En lugar de explicar qué volatile Sí, permíteme explicarte cuándo deberías usar volatile.

  • Cuando está dentro de un manejador de señal. Porque escribiendo a un volatile variable es prácticamente lo único que el estándar le permite hacer desde un manejador de señal. Desde C ++ 11 puedes usar std::atomic para ese propósito, pero solo si el atómico está libre de cerrojo.
  • Cuando se trata de setjmp  de acuerdo con Intel.
  • Al tratar directamente con el hardware y desea asegurarse de que el compilador no optimice sus lecturas o escrituras.

Por ejemplo:

volatile int *foo = some_memory_mapped_device;
while (*foo)
    ; // wait until *foo turns false

Sin el volatile especificador, el compilador puede optimizar completamente el bucle. los volatile el especificador le dice al compilador que no puede suponer que 2 lecturas posteriores devuelven el mismo valor.

Tenga en cuenta que volatile no tiene nada que ver con los hilos. El ejemplo anterior no funciona si había una escritura de subproceso diferente en *foo porque no hay una operación de adquisición involucrada.

En todos los demás casos, el uso de volatile debe considerarse no portátil y no pasar la revisión del código más, excepto cuando se trata de compiladores anteriores a C ++ 11 y extensiones del compilador (como msvc's /volatile:ms interruptor, que está habilitado por defecto en X86 / I64).


45
2017-10-10 21:44



¿La palabra clave volátil de C ++ introduce una valla de memoria?

No se requiere un compilador de C ++ que cumpla con la especificación para introducir una valla de memoria. Tu compilador particular podría; dirige tu pregunta a los autores de tu compilador.

La función de "volátil" en C ++ no tiene nada que ver con el enhebrado. Recuerde, el propósito de "volátil" es desactivar las optimizaciones del compilador para que la lectura de un registro que está cambiando debido a condiciones exógenas no se optimice. ¿Es una dirección de memoria en la que se escribe un hilo diferente en una CPU diferente, un registro que está cambiando debido a condiciones exógenas? No. Una vez más, si algunos compiladores tienen elegido para tratar las direcciones de memoria que se escriben por diferentes subprocesos en diferentes CPU como si fueran registros que cambian debido a condiciones exógenas, ese es su negocio; no están obligados a hacerlo. Tampoco son necesarios, incluso si introduce una valla de memoria, para, por ejemplo, garantizar que cada hilo ve una consistente ordenamiento de lecturas y escrituras volátiles.

De hecho, volátil es bastante inútil para enhebrar en C / C ++. La mejor práctica es evitarlo.

Además: las vallas de memoria son un detalle de implementación de arquitecturas de procesador particulares. En C #, donde volátil explícitamente es diseñado para multihilo, la especificación no dice que se introducirán medias vallas, porque el programa podría estar ejecutándose en una arquitectura que no tiene vallas en primer lugar. Más bien, una vez más, la especificación proporciona ciertas garantías (extremadamente débiles) sobre qué optimizaciones evitará el compilador, el tiempo de ejecución y la CPU para poner ciertas restricciones (extremadamente débiles) sobre cómo se ordenarán algunos efectos secundarios. En la práctica, estas optimizaciones se eliminan mediante el uso de medias vallas, pero eso es un detalle de implementación sujeto a cambios en el futuro.

El hecho de que usted se preocupe por la semántica de los volátiles en cualquier idioma, ya que pertenecen al multihilo, indica que está pensando en compartir la memoria entre los hilos. Considera simplemente no hacer eso. Hace que tu programa sea mucho más difícil de comprender y que sea mucho más probable que contenga errores sutiles e imposibles de reproducir.


20
2017-10-11 13:39



Lo que David está pasando por alto es el hecho de que el estándar c ++ especifica el comportamiento de varios hilos que interactúan solo en situaciones específicas y todo lo demás da como resultado un comportamiento indefinido. Una condición de carrera que implique al menos una escritura no está definida si no utiliza variables atómicas.

En consecuencia, el compilador tiene todo el derecho a renunciar a las instrucciones de sincronización, ya que su CPU solo notará la diferencia en un programa que muestra un comportamiento indefinido debido a la falta de sincronización.


12
2017-10-10 22:36



En primer lugar, los estándares de C ++ no garantizan las barreras de memoria necesarias para ordenar adecuadamente las lecturas / escrituras que no son atómicas. volátil Se recomiendan variables para usar con MMIO, manejo de señal, etc. En la mayoría de las implementaciones volátil no es útil para multi-threading y generalmente no se recomienda.

En cuanto a la implementación de accesos volátiles, esta es la opción del compilador.

Esta artículo, describiendo gcc el comportamiento muestra que no puede usar un objeto volátil como barrera de memoria para ordenar una secuencia de escrituras en la memoria volátil.

Respecto a icc comportamiento encontré esto fuente diciendo también que volátil no garantiza el pedido de accesos de memoria.

Microsoft VS2013 el compilador tiene un comportamiento diferente. Esta documentación explica cómo la volatilidad impone la semántica de Liberar / Adquirir y permite que los objetos volátiles se usen en bloqueos / liberaciones en aplicaciones de subprocesos múltiples.

Otro aspecto que debe tenerse en cuenta es que el mismo compilador puede tener un comportamiento diferente wrt. volátil dependiendo de la arquitectura de hardware específica. Esta enviar con respecto al compilador MSVS 2013 establece claramente los detalles de la compilación con las plataformas volátiles para ARM.

Entonces mi respuesta a:

¿La palabra clave volátil de C ++ introduce una valla de memoria?

sería: No garantizado, probablemente no, pero algunos compiladores podrían hacerlo. No deberías confiar en el hecho de que sí.


12
2017-10-10 19:55



El compilador solo inserta una valla de memoria en la arquitectura Itanium, hasta donde yo sé.

los volatile la palabra clave realmente se usa mejor para cambios asincrónicos, por ejemplo, manejadores de señal y registros mapeados en memoria; por lo general, es la herramienta incorrecta para usar en la programación multiproceso.


7
2017-10-10 19:55



Depende de qué compilador sea "el compilador". Visual C ++ lo hace, desde 2005. Pero el estándar no lo requiere, por lo que otros compiladores no lo requieren.


6
2017-10-10 20:03



Esto es en gran medida de la memoria, y se basa en pre-C ++ 11, sin hilos. Pero habiendo participado en las discusiones sobre el roscado en el comité, puedo decir que nunca hubo una intención por parte del comité que volatile podría ser utilizado para sincronización entre hilos. Microsoft lo propuso, pero la propuesta no llevó.

La especificación clave de volatile es que el acceso a un elemento volátil representa una "comportamiento observable", al igual que IO. De la misma manera, el compilador no puede reordenar o eliminar IO específico, no puede reordenar o eliminar accesos a un objeto volátil (o más correctamente, accede a través de una expresión lvalue con tipo calificado volátil). La intención original de volátil era, de hecho, memoria de soporte asignada IO. El "problema" con esto, sin embargo, es que es implementación definió qué constituye un "acceso volátil". Y muchos los compiladores lo implementan como si la definición fuera "una instrucción que lee o escribe en la memoria se ha ejecutado ". Que es legal, aunque inútil definición, Si la implementación lo especifica. (Todavía tengo que encontrar el real especificación para cualquier compilador).

Podría decirse que (y es un argumento que acepto), esto viola la intención del estándar, ya que a menos que el hardware reconozca las direcciones como memoria asignada IO, e inhibe cualquier reordenamiento, etc., ni siquiera puede usar volátiles para la memoria E / S mapeado, al menos en arquitecturas Sparc o Intel. Sin embargo, ninguno de los "comilers" que he analizado (Sun CC, g ++ y MSC) producen cualquier cerca o membar instrucciones. (Sobre el momento en que Microsoft propuso extender las reglas para volatile, Creo que algunos de sus compiladores implementaron su propuesta, y lo hicieron emitir instrucciones de cerca para accesos volátiles. No he verificado lo reciente compiladores, pero no me sorprendería si dependiera de algún compilador opción. La versión que compré, creo que era VS6.0, no emitía vallas, sin embargo)


5
2017-10-12 11:30



No es necesario. Volátil no es una primitiva de sincronización. Simplemente desactiva las optimizaciones, es decir, obtiene una secuencia predecible de lecturas y escrituras dentro de un hilo en el mismo orden prescrito por la máquina abstracta. Pero las lecturas y escrituras en diferentes hilos no tienen orden en primer lugar, no tiene sentido hablar de preservar o no preservar su orden. El orden entre los puntos se puede establecer mediante primitivas de sincronización, obtienes UB sin ellos.

Un poco de explicación con respecto a las barreras de memoria. Una CPU típica tiene varios niveles de acceso a la memoria. Hay una tubería de memoria, varios niveles de caché, luego RAM, etc.

Las instrucciones Membar limpian la tubería. No cambian el orden en que se ejecutan las lecturas y escrituras, solo obliga a las ejecuciones pendientes a ejecutarse en un momento dado. Es útil para programas multiproceso, pero no mucho de lo contrario.

Las caché (s) normalmente son automáticamente coherentes entre las CPU. Si uno quiere asegurarse de que la memoria caché esté sincronizada con la memoria RAM, se requiere la limpieza de la memoria caché. Es muy diferente de un membar.


5
2017-10-13 00:27



El compilador necesita introducir una valla de memoria alrededor volatile accede si, y solo si, eso es necesario para hacer los usos para volatile especificado en el trabajo estándar (setjmp, controladores de señal, etc.) en esa plataforma en particular.

Tenga en cuenta que algunos compiladores van mucho más allá de lo que exige el estándar C ++ para hacer volatile más poderoso o útil en esas plataformas. El código portátil no debe confiar en volatile hacer algo más allá de lo especificado en el estándar C ++.


4
2017-10-14 22:19