¿Se garantiza que `*(volatile T*)0x1234;` se traduzca en instrucciones de lectura?

6 minutos de lectura

avatar de usuario
Eugenio Sh.

Cuando se trabaja con hardware, a veces se requiere realizar una lectura de un registro específico descartando el valor real (para borrar algunas banderas, por ejemplo). Una forma sería leer y descartar explícitamente el valor como:

int temp = *(volatile int*)0x1234; // 0x1234 is the register address
(void)temp;                          // To silence the "unused" warning

Otra forma que parece funcionar es simplemente:

*(volatile int*)0x1234;

Pero esto no parece implicar obviamente la leer acceso, sin embargo, parece traducirse a uno en los compiladores que verifiqué. ¿Está esto garantizado por la norma?

Ejemplo para ARM GCC con -O3:
https://arm.godbolt.org/z/9Vmt6n

void test(void)
{
    *(volatile int *)0x1234;
}

se traduce como

test():
        mov     r3, #4096
        ldr     r3, [r3, #564]
        bx      lr

  • Creo que podría haber escrito una pregunta muy similar basada en escribir en un puerto o registro. La desreferenciación de la dirección por sí misma no implica ni lectura ni escritura.

    – Tim Randall

    6 de diciembre de 2018 a las 19:49

  • @TimRandall De ahí la pregunta, ya que parece que incluso las optimizaciones más agresivas lo están traduciendo en una instrucción de lectura.

    – Eugenio Sh.

    06/12/2018 a las 19:50

  • Puede ser útil enumerar los compiladores y/o el hardware en el que ha probado esto. es una pregunta interesante

    – Tim Randall

    6 de diciembre de 2018 a las 19:52

  • Actualizado con un ejemplo específico

    – Eugenio Sh.

    6 de diciembre de 2018 a las 19:54

  • Bastante, sí. Porque es volatile. Sin volatileestá marcado como warning: statement with no effect [-Wunused-value]. Para ser totalmente claro, podrías hacer: (void) *(volatile int *)0x1234;

    –Craig Estey

    6 de diciembre de 2018 a las 20:04

avatar de usuario
Eric Pospischil

C 2018 6.7.3 8 dice:

Un objeto que tiene un tipo calificado como volátil puede modificarse de formas desconocidas para la implementación o tener otros efectos secundarios desconocidos. Por lo tanto, cualquier expresión que se refiera a tal objeto se evaluará estrictamente de acuerdo con las reglas de la máquina abstracta, como se describe en 5.1.2.3.…

Ya que *(volatile int*)0x1234; es una expresión que se refiere a un objeto con tipo calificado volátil, evaluándolo debe acceder al objeto. (Esto supone que 0x1234 representa una referencia válida a algún objeto en la implementación de C, por supuesto).

Según C 2018 5.1.2.3 4:

En la máquina abstracta, todas las expresiones se evalúan según lo especificado por la semántica. Una implementación real no necesita evaluar parte de una expresión si puede deducir que su valor no se usa y que no se producen efectos secundarios necesarios (incluidos los causados ​​por llamar a una función o acceder a un objeto volátil).

Según C 2018 6.5 1:

Una expresión es una secuencia de operadores y operandos que especifica el cálculo de un valor, o que designa un objeto o una función, o que genera efectos secundarios, o que realiza una combinación de los mismos.

Por lo tanto, una expresión especifica el cálculo de un valor. El párrafo 5.1.2.3 4 nos dice que esta evaluación la realiza la máquina abstracta, y el 6.7.3 8 nos dice que la implementación real realiza esta evaluación que realiza la máquina de abstracción.

Una advertencia es que lo que constituye “acceso” está definido por la implementación. El “acceso”, tal como lo define el estándar C, incluye tanto la lectura como la escritura (C 3.1 1), pero el estándar C no puede especificar que signifique leer o escribir en una determinada pieza de hardware.

Para profundizar en lenguaje-abogado, territorio, C 6.3.2.1 2 nos dice:

Excepto cuando es el operando del sizeof operador, el unario & operador, el ++ operador, el -- operador, o el operando izquierdo del . operador o un operador de asignación, un lvalue que no tiene un tipo de matriz se convierte en el valor almacenado en el objeto designado (y ya no es un lvalue); esto se llama conversión de lvalue.

Así, desde *(volatile int*)0x1234; es un lvalue, a fuerza de la * y no es el operando de los operadores enumerados, se convierte al valor almacenado en el objeto. Por lo tanto, esta expresión especifica el cálculo del valor que se almacena en el objeto.

La documentación de gcc en volátil nos dice que lo que constituye un acceso volátil está definido por la implementación:

C tiene el concepto de objetos volátiles. Normalmente se accede a ellos mediante punteros y se utilizan para acceder al hardware o la comunicación entre subprocesos. El estándar alienta a los compiladores a abstenerse de optimizar los accesos a objetos volátiles, pero deja la implementación definida en cuanto a lo que constituye un acceso volátil. El requisito mínimo es que en un punto de secuencia todos los accesos anteriores a objetos volátiles se hayan estabilizado y no se hayan producido accesos posteriores. Por lo tanto, una implementación es libre de reordenar y combinar accesos volátiles que ocurren entre puntos de secuencia, pero no puede hacerlo para accesos a través de un punto de secuencia. El uso de volatile no le permite violar la restricción de actualizar objetos varias veces entre dos puntos de secuencia.

Esto está respaldado por la sección C11 6.7.3 Calificadores de tipo p7:

Un objeto que tiene un tipo calificado como volátil puede modificarse de formas desconocidas para la implementación o tener otros efectos secundarios desconocidos. Por lo tanto, cualquier expresión que se refiera a dicho objeto se evaluará estrictamente de acuerdo con las reglas de la máquina abstracta, como se describe en 5.1.2.3. Además, en cada punto de la secuencia, el último valor almacenado en el objeto deberá coincidir con el prescrito por la máquina abstracta, excepto que sea modificado por los factores desconocidos mencionados anteriormente.134) Lo que constituye un acceso a un objeto que tiene un tipo calificado como volátil está definido por la implementación.

El documento gcc continúa especificando cómo funciona volatile para gcc, para el caso similar al tuyo dice:

Un objeto volátil escalar se lee cuando se accede a él en un contexto vacío:

volatile int *src = somevalue;
*src;

Tales expresiones son rvalues, y GCC implementa esto como una lectura del objeto volátil al que se apunta.

  • La última cita es la más interesante, ya que en realidad responde “sí” a la pregunta original de GCC.

    – Eugenio Sh.

    6 de diciembre de 2018 a las 20:47

  • Curiosamente, “acceso” es un término definido en el estándar: “para leer o modificar el valor de un objeto”. Por lo tanto, no me inclino a tomar la definición de implementación de los accesos volátiles como absoluta (lo que sería discutible en la parte anterior del párrafo 6.7.3/7). De hecho, no estoy del todo seguro de cómo reconciliar esa definición con la disposición de definición de implementación al final de 6.7.3/7.

    – John Bollinger

    6 de diciembre de 2018 a las 21:28


  • Sin embargo, al menos GCC parece tener todas sus bases cubiertas. Proporciona y documenta el comportamiento en esta área que parece ser justo lo que requeriría el estándar si ignoramos la definición de implementación.

    – John Bollinger

    6 de diciembre de 2018 a las 21:32


¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad