studio - ¿Cómo forzar una lectura de memoria no utilizada en C que no se optimizará?
programacion avr (5)
Los microcontroladores a menudo requieren que se lea un registro para borrar ciertas condiciones de estado. ¿Hay una forma portátil en C para garantizar que una lectura no se optimice si no se utilizan los datos? ¿Es suficiente que el puntero al registro mapeado de memoria se declare como volátil? En otras palabras, ¿lo siguiente siempre funcionará en compiladores compatibles con los estándares?
void func(void)
{
volatile unsigned int *REGISTER = (volatile unsigned int *) 0x12345678;
*REGISTER;
}
Entiendo que tratar con una funcionalidad como esta se ejecuta en problemas dependientes del compilador. Por lo tanto, mi definición de portátil es un poco floja en este caso. Solo quiero decir que funcionaría lo más ampliamente posible con las cadenas de herramientas más populares.
IIRC, el estándar C es un poco flojo en la definición de uso, por lo que el *REGISTER
no se interpreta necesariamente como una lectura .
Pero lo siguiente debe hacer:
int x = *REGISTER;
Es decir, el resultado de la referencia de memoria debe usarse en algún lugar. Sin embargo, la x
no necesita ser volátil.
ACTUALIZACIÓN : Para evitar la advertencia de la variable _unused, podría hacerlo con una función sin operación. Una función estática y / o en línea se debe optimizar sin penalización de tiempo de ejecución:
static /*inline*/ void no_op(int x)
{ }
no_op(*REGISTER);
ACTUALIZACIÓN 2 : Acabo de llegar a una función más agradable:
static unsigned int read(volatile unsigned int *addr)
{
return *addr;
}
read(REGISTER);
Ahora, esta función se puede usar tanto para leer y usar como para leer y descartar. 8-)
La gente discute bastante enérgicamente sobre qué significa volatile
. Creo que la mayoría de la gente está de acuerdo en que la construcción que usted muestra tenía la intención de hacer lo que quiere, pero no hay un acuerdo general de que el lenguaje en el estándar C realmente lo garantice a partir de C99. (La situación puede haber mejorado en C2011; aún no he leído eso).
Una alternativa no estándar, pero bastante ampliamente soportada por compiladores integrados, que puede ser más probable que funcione, es
void func(void)
{
asm volatile ("" : : "r" (*(unsigned int *)0x12345678));
}
(El término ''volátil'' se aplica al ''asm'' y significa ''esto no se puede eliminar a pesar de que no tiene operandos de salida. No es necesario colocarlo en el puntero también).
El principal inconveniente que queda de esta construcción es que aún no tiene garantía de que el compilador genere una lectura de memoria de una sola instrucción . Con C2011, el uso de _Atomic unsigned int
puede ser suficiente, pero en ausencia de esa característica, tiene que escribir un ensamblaje real (no vacío) si necesita esa garantía.
EDITAR: Otra arruga se me ocurrió esta mañana. Si leer desde la ubicación de la memoria tiene el efecto secundario de cambiar el valor en esa ubicación de memoria, necesita
void func(void)
{
unsigned int *ptr = (unsigned int *)0x12345678;
asm volatile ("" : "=m" (*ptr) : "r" (*ptr));
}
para evitar la mala optimización de otras lecturas de esa ubicación. (Para ser 100% claros, este cambio no cambiará el lenguaje ensamblador generado para la propia func
, pero puede afectar la optimización del código circundante, especialmente si la func
está en línea).
Los compiladores generalmente no optimizan las líneas de ensamblaje (es difícil analizarlos correctamente). Además, parece ser una solución adecuada: desea un control más explícito sobre los registros y es natural para el ensamblaje.
Como está programando un microcontrolador, asumo que ya hay algún ensamblaje en su código, por lo que un poco de ensamblaje en línea no será un problema.
Quizás las extensiones específicas de GNU C no se consideran muy portátiles, pero aquí hay otra alternativa.
#define read1(x) /
({ /
__typeof(x) * _addr = (volatile __typeof(x) *) &(x); /
*_addr; /
})
Esto se traducirá a la siguiente línea de ensamblador (compilada con gcc x86 y optimizada con -O2): movl SOME_REGISTER(%rip), %eax?
Obtengo el mismo ensamblador de:
inline read2(volatile uint32_t *addr)
{
return *addr;
}`
... como se sugiere en otra respuesta, pero read1()
manejará diferentes tamaños de registro. Aunque no estoy seguro de que el uso de read2()
con registros de 8 o 16 bits sea un problema, al menos no hay advertencias sobre el tipo de parámetro.
Sí, el estándar C garantiza que el código que accede a una variable volátil no se optimizará.
C11 5.1.2.3/2
"Acceder a un objeto volátil," ... "son todos efectos secundarios"
C11 5.1.2.3/4
"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 los efectos secundarios necesarios (incluidos los causados por llamar a una función o acceder a un objeto volátil)".
C11 5.1.2.3/6
"Los requisitos mínimos para una implementación conforme son:
- Los accesos a objetos volátiles se evalúan estrictamente de acuerdo con las reglas de la máquina abstracta ".