Lenguaje C: el papel de la palabra clave volátil

I. Introducción

1. Introducción a la optimización del compilador

Debido a que la velocidad de acceso a la memoria es mucho más baja que la velocidad de procesamiento de la CPU, para mejorar el rendimiento general de la máquina, se introduce una caché de hardware de alta velocidad en el hardware para acelerar el acceso a la memoria. Además, la ejecución de instrucciones en las CPU modernas no se ejecuta necesariamente en un orden estricto, y las instrucciones sin correlación se pueden ejecutar fuera de orden para hacer un uso completo de la canalización de instrucciones de la CPU y aumentar la velocidad de ejecución. Lo anterior es la optimización del nivel de hardware .

Mire la optimización a nivel de software : una es optimizada por el programador al escribir el código y la otra es optimizada por el compilador. 编译器优化常用的方法有:将内存变量缓存到寄存器;调整指令顺序充分利用CPU指令流水线,常见的是重新排序读写指令。Al optimizar la memoria convencional, estas optimizaciones son transparentes y eficientes. La solución a los problemas causados ​​por la optimización del compilador o el reordenamiento del hardware es establecer una barrera de memoria entre las operaciones que deben realizarse en un orden específico desde la perspectiva del hardware (u otros procesadores). Linux proporciona una solución macro. El orden de ejecución del el compilador es un problema.

void Barrier(void)

Esta función informa al compilador que inserte una barrera de memoria, pero no es válida para el hardware. El código compilado almacenará todos los valores modificados en el registro actual de la CPU en la memoria, y luego los leerá de la memoria nuevamente cuando los datos es necesario.

2 、 volátil

Lo volátil siempre está relacionado con la optimización. El compilador tiene una técnica llamada análisis de flujo de datos, que analiza dónde se asignan las variables en el programa, dónde se usan y dónde fallan. Los resultados del análisis se pueden usar para fusión constante, propagación constante y otras optimizaciones, y pueden eliminarse aún más. Pero a veces estas optimizaciones no son requeridas por el programa y pueden usarse en este momento volatile关键字禁止做这些优化.

Dos, explicación detallada de volátiles

1. Función volátil

La intención original de volatile es “易变的”que debido a que el acceso a los registros es mucho más rápido que el acceso a las celdas de memoria, el compilador generalmente optimiza para reducir el acceso a la memoria, pero puede leer datos sucios. 当要求使用volatile声明变量值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据. Para ser precisos, al encontrar una variable declarada por esta palabra clave, el compilador ya no optimizará el código que accede a la variable, de modo que proporcione acceso estable a direcciones especiales; si no se usa volatile, el compilador hará la declaración optimizada. (Para decirlo de manera sucinta: la palabra clave volatile afecta el resultado de la compilación del compilador. La variable declarada con volatile indica que la variable puede cambiar en cualquier momento. Para operaciones relacionadas con la variable, no realice optimización de compilación para evitar errores)

2. Mira dos ejemplos

1> 告诉compiler不能做任何优化

Por ejemplo, para enviar dos comandos a una determinada dirección:

int *ip =...; //设备地址
*ip = 1; //第一个指令
*ip = 2; //第二个指令

El compilador del programa anterior se puede optimizar:

int *ip = ...;
*ip = 2;

Como resultado, se pierde la primera instrucción. Si se usa volatile, el compilador no permitirá ninguna optimización para garantizar la intención original del programa:

volatile int *ip = ...;
*ip = 1;
*ip = 2;

Incluso si desea que el compilador optimice, no fusionará las dos instrucciones de asignación en una. Solo puede hacer otras optimizaciones.

2> 用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能重复使用放在cache或寄存器中的备份。

P.ej:

volatile char a;

a=0;

while(!a){
    
    
//do some things;
}

doother();

如果没有 volatile,doother()不会被执行

3. Varios escenarios para el uso de variables volátiles

1> 中断服务程序中修改的供其它程序检测的变量需要加volatile;

P.ej:

static int i=0;

int main(void)
{
    
    
     ...
     while (1){
    
    
		if (i) dosomething();	
	}
}

/* Interrupt service routine. */

void ISR_2(void)
{
    
    
      i=1;
}

La intención de ISR_2 es ​​programar una interrupción se genera, la función llama a algo función principal, sin embargo, debido a que el compilador determina que no cambia dentro de la función principal i, se puede realizar solo una vez a partir de ila operación de lectura de un registro, y cada una ifse determina Todo solo utilizar el contenido de este registro “i副本”, haciendo que nunca se llame a dosomething. Si agrega la variable volatilemodificada, el compilador lee y escribe variables para asegurarse de que no se optimizará (ejecución positiva). En este ejemplo idebería establecerse así.

2>多任务环境下各任务间共享的标志应该加volatile

3>存储器映射的硬件寄存器通常也要加voliate,因为每次对它的读写都可能有不同意义。

P.ej:

Suponga que se va a inicializar un dispositivo y un determinado registro de este dispositivo es 0xff800000.

int *output = (unsigned int *)0xff800000;//定义一个IO端口;

int init(void)
{
    
    
	int i;
      
	for(i=0; i< 10; i++){
    
    
		*output = i;
	}
}

Después de la optimización por parte del compilador, el compilador piensa que el bucle anterior no tiene sentido durante medio día y no tiene ningún efecto en el resultado final, porque al final solo asigna el puntero de salida a 9, por lo que el compilador finalmente le da el compilado el resultado del código es equivalente a:

int init(void)
{
    
    
	*output = 9;
}

Si inicializa el dispositivo externo en el mismo orden que el código anterior, obviamente el proceso de optimización no logrará el objetivo. Por el contrario, si no repite las operaciones de escritura en este puerto, pero repite las operaciones de lectura, el resultado es el mismo. Después de optimizar el compilador, quizás su código lea esta dirección solo una vez. Sin embargo, desde el punto de vista del código, no hay problema. En este momento con respecto al uso volatilepara informar al compilador que la variable es inestable, no para optimizar la experiencia esta variable de tiempo.

P.ej:

volatile  int *output=(volatile unsigned int *)0xff800000;//定义一个I/O端口

Además, las diversas situaciones anteriores a menudo deben considerarse al mismo tiempo 数据的完整性(los signos interrelacionados se leen a medias, se interrumpen y se reescriben) 1中可以通过关中断来实现,2中禁止任务调度,3中则只能依靠硬件的良好设计.

4. Varias preguntas

  • 1) ¿Puede un parámetro ser constante o volátil?

Sí, como un registro de estado de solo lectura. Es volátil porque se puede cambiar inesperadamente. Es constante porque el programa no debería intentar modificarlo.

  • 2) ¿Puede un puntero ser volátil?

Sí, cuando una rutina de servicio de interrupción modifica un puntero a un búfer.

5. La esencia de volátiles

1> Optimización del compilador

En este hilo, al leer una variable, para mejorar la velocidad de acceso, el compilador a veces lee la variable en un registro al optimizar; luego, cuando se toma el valor de la variable, el valor se toma directamente del registro; cuando la variable Si cambia el valor en este hilo, el nuevo valor de la variable se copiará al registro al mismo tiempo, para mantener la coherencia.

Cuando el valor de la variable cambia debido a otros subprocesos, etc., el valor del registro no cambiará en consecuencia, causando que el valor leído por el programa de aplicación sea inconsistente con el valor real de la variable.

Cuando el valor de este registro cambia debido a otros subprocesos, etc., el valor de la variable original no cambiará, lo que hará que el valor leído por el programa de aplicación sea inconsistente con el valor real de la variable.

2> volátil debe interpretarse como “直接存取原始内存地址”más apropiado, "variable" Esta interpretación es simplemente un poco engañosa.

6. ¿Qué hay de malo en la siguiente función?

int square(volatile int *ptr)
{
    
    
	return *ptr * *ptr;
}

El propósito de este programa es devolver el cuadrado del valor al que apunta el puntero ptr.Sin embargo, dado que ptr apunta a un parámetro volátil, el compilador generará un código similar al siguiente:

int square(volatile int *ptr)
{
    
    
	int a,b;
	a = *ptr;
	b = *ptr;
	return a * b;
}

Dado que el valor de * ptr puede cambiarse inesperadamente, ayb pueden ser diferentes. Como resultado, este código puede devolver un valor cuadrado que no es el esperado. El código correcto es el siguiente:

long square(volatile int *ptr)
{
    
    
	int a;
	a = *ptr;
	return a * a;
}

注意:频繁地使用volatile很可能会增加代码尺寸和降低性能,因此要合理的使用volatile。

Supongo que te gusta

Origin blog.csdn.net/houxiaoni01/article/details/105163980
Recomendado
Clasificación