c

¿Es la función llamada una barrera de memoria?



(5)

Aquí hay un ejemplo ligeramente modificado, compilado con gcc 7.2.1 en x86-64:

#include <string.h> static int temp; extern volatile int hardware_reg; int foo (int x) { hardware_reg = 0; memcpy(&temp, &x, sizeof(int)); hardware_reg = 1; return temp; }

gcc sabe que memcpy() es lo mismo que una asignación, y sabe que no se accede a temp ningún otro lugar, así que temp y memcpy() desaparecen completamente del código generado

foo: movl $0, hardware_reg(%rip) movl %edi, %eax movl $1, hardware_reg(%rip) ret

Considere este código C:

extern volatile int hardware_reg; void f(const void *src, size_t len) { void *dst = <something>; hardware_reg = 1; memcpy(dst, src, len); hardware_reg = 0; }

La llamada memcpy() debe ocurrir entre las dos asignaciones. En general, dado que el compilador probablemente no sepa qué hará la función llamada, no puede reordenar la llamada a la función para que esté antes o después de las asignaciones. Sin embargo, en este caso el compilador sabe lo que hará la función (e incluso podría insertar un sustituto integrado en línea), y puede deducir que memcpy() nunca podría acceder a hardware_reg . Aquí me parece que el compilador no vería ningún problema en mover la llamada memcpy() , si quisiera hacerlo.

Entonces, la pregunta: ¿es una llamada de función solo lo suficiente como para emitir una barrera de memoria que impida la reordenación, o es, de lo contrario, una barrera de memoria explícita necesaria en este caso antes y después de la llamada a memcpy() ?

Por favor, corrígeme si estoy entendiendo mal las cosas.


El compilador no puede reordenar la operación memcpy() antes de hardware_reg = 1 o después de hardware_reg = 0 , eso es lo que asegurará la volatile , al menos en la secuencia de instrucciones que emite el compilador. Una llamada de función no es necesariamente una ''barrera de memoria'', pero es un punto de secuencia.

El estándar C99 dice esto sobre la volatile (5.1.2.3/5 "Ejecución del programa"):

En los puntos de secuencia, los objetos volátiles son estables en el sentido de que los accesos anteriores están completos y los accesos posteriores aún no se han producido.

Por lo tanto, en el punto de secuencia representado por memcpy() , el acceso volátil de la escritura 1 tiene que ocurrir, y el acceso volátil de la escritura 0 no puede haber ocurrido.

Sin embargo, hay 2 cosas que me gustaría señalar:

  1. Dependiendo de lo que sea <something> , si no se hace nada más con el búfer de destino, el compilador podría eliminar completamente la operación memcpy() . Esta es la razón por la que Microsoft creó la función SecureZeroMemory() . SecureZeroMemory() funciona con punteros cualificados volatile para evitar la optimización de las escrituras.

  2. volatile no implica necesariamente una barrera de memoria (que es una cuestión de hardware, no solo una cosa de pedido de código), por lo que si está ejecutando en una máquina con múltiples procesos o en ciertos tipos de hardware es posible que necesite invocar explícitamente una barrera de memoria (quizás wmb() en Linux).

    A partir de MSVC 8 (VS 2005), Microsoft documenta que la palabra clave volatile implica la barrera de memoria adecuada, por lo que puede que no sea necesaria una llamada de barrera de memoria específica por separado:

    Además, al optimizar, el compilador debe mantener el orden entre las referencias a objetos volátiles, así como las referencias a otros objetos globales. En particular,

    • Una escritura en un objeto volátil (escritura volátil) tiene una semántica de lanzamiento; una referencia a un objeto global o estático que se produce antes de una escritura en un objeto volátil en la secuencia de instrucciones se producirá antes de esa escritura volátil en el binario compilado.

    • Una lectura de un objeto volátil (lectura volátil) tiene la semántica de Adquirir; una referencia a un objeto global o estático que ocurre después de una lectura de memoria volátil en la secuencia de instrucciones ocurrirá después de esa lectura volátil en el binario compilado.


Es probable que se optimice, ya sea porque el compilador alinea la llamada de la máquina y elimina la primera asignación, o porque se compila en el código RISC o en el código de la máquina y se optimiza allí.


Hasta donde puedo ver su razonamiento que lleva a

el compilador no vería ningún problema en mover la llamada memcpy

es correcto. La definición de idioma no responde a su pregunta y solo puede abordarse con referencia a compiladores específicos.

Lo siento por no tener ninguna información más útil.


Supongo que el compilador nunca reordena las asignaciones volátiles, ya que debe asumir que deben ejecutarse exactamente en la posición en la que aparecen en el código.