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

Considere este código C:

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

La llamada memcpy() debe ocurrir entre las dos asignaciones. En general, dado que el comstackdor 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 comstackdor 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 comstackdor no vería problemas en mover la llamada memcpy() , si quisiera hacerlo.

Entonces, la pregunta: ¿es una llamada de función solo lo suficiente 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 comstackdor 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 comstackdor. 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 progtwig”):

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 debe 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 , si no se hace nada más con el búfer de destino, el comstackdor 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 multi-proc 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 apropiada, por lo que puede que no sea necesaria una llamada de barrera de memoria específica separada:

    Además, al optimizar, el comstackdor 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 comstackdo.

    • Una lectura de un objeto volátil (lectura volátil) tiene 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 comstackdo.

Hasta donde puedo ver su razonamiento que lleva a

el comstackdor 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 comstackdores específicos.

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

Supongo que el comstackdor 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.

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

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

 #include  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, por lo 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