Análisis de palabras clave volátiles y const en lenguaje C

volátil

1. Explicación de palabras clave:

El significado original de volátil es "volátil"
porque el acceso al registro es mucho más rápido que el acceso a la unidad de memoria, por lo que el compilador generalmente optimiza para reducir el acceso a la memoria, pero puede leer datos sucios. Cuando se requiere que volatile declare el valor de una variable, el sistema siempre lee datos de la memoria donde se encuentra, incluso si la instrucción anterior acaba de leer datos de ella. 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, para proporcionar un 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)

En términos generales, volatile se usa en los siguientes lugares:
1) Las variables modificadas en el programa de servicio de interrupciones para su detección por otros programas necesitan agregar volátiles;
2) Los indicadores compartidos entre tareas en un entorno multitarea deben agregar volátiles;
3) Memoria El hardware mapeado Por lo general, el registro también necesita agregar una descripción volátil, porque cada vez que se lee y escribe, puede tener diferentes significados.
Sin explicar por qué se usa volátil en estas situaciones, echemos un vistazo al origen:
hardware
Dado que la velocidad de acceso a la memoria es mucho menor que la CPU, para mejorar el rendimiento, la caché de hardware se introduce en el hardware para acelerar el acceso a la memoria. La ejecución de instrucciones en la CPU no se ejecuta necesariamente en un orden estricto. Si no hay instrucciones relevantes, se pueden ejecutar fuera de orden para hacer un uso completo de la canalización de instrucciones de la CPU (cascada) para aumentar la velocidad de ejecución. Optimización del
software de
primer nivel: uno es optimizado por el programador al escribir el código y el otro es optimizado por el compilador. Los métodos comúnmente utilizados para la optimización del compilador son: variables de memoria caché a registros; ajuste el orden de las instrucciones para hacer un uso completo de la canalización de instrucciones de la CPU, y el común es reordenar las instrucciones de lectura y escritura. 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 otro procesador). ** 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 los registros de la CPU actual en la memoria. se necesitan datos, vuelva a leer de la memoria.
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 algunas pueden eliminarse aún más. Pero a veces estas optimizaciones no son lo que necesita el programa, entonces puede usar la palabra clave volátil para prohibir estas optimizaciones.
2. Un ejemplo simple para explicar por qué se debe usar volátil
1) Dígale al compilador que no se puede realizar ninguna optimización

int i;
i=1;
i=2;
i=3;

Una vez compilado el compilador, se optimizará como:

int i;
i=3;

Hacer que el valor asignado 1, 2 se optimice. Para asegurar la intención original, debería ser así:

volatile int i;
i=1;
i=2;
i=3;

2) Las variables definidas por volátiles se cambiarán fuera del programa y deben leerse de la memoria cada vez, y las copias de seguridad colocadas en la caché o los registros no se pueden reutilizar.
Condición : en multitarea (en multiproceso), que es el segundo punto mencionado anteriormente, hay un bit de bandera en multiproceso, y es posible modificarlo

volatile int  a;
a=0;
while(!a){
    
    
do some things;
}
init();

Si otras tareas no modifican a, la función init () no se ejecutará, y también se ejecutará de acuerdo a los requerimientos del programador, y no se requiere volátil. Por el contrario, si el valor de la variable a se modifica en otro tareas, se requiere tiempo real Para actualizar los datos en el registro, se deben agregar volátiles.
3. Utilice ejemplos para explicar los tres escenarios en los que se utiliza volátil:
1) Las variables modificadas en el programa de servicio de interrupciones para que sean detectadas por otros programas deben ser volátiles.

static int a=0;
int main(void)
{
    
    
     while (1){
    
    
if (a) 
dosomething();
              }
void IRQ(void)//中断函数
{
    
    
      a=1;
}

La intención original del programa es llamar a la función dosomething en la función principal cuando se genera la interrupción IRQ.Sin embargo, dado que el compilador considera que a no se ha modificado en la función principal, solo puede realizar una operación de lectura de a a un cierto registro, y luego cada segundo si el juicio solo usa "una copia" en este registro, lo que hace que nunca se llame a dosomething. Si la variable se modifica con volatile, el compilador garantiza que las operaciones de lectura y escritura en esta variable no serán optimizadas (definitivamente ejecutadas).
El código correcto es el siguiente

volatile static int a=0;
int main(void)
{
    
    
     while (1){
    
    
if (a) 
dosomething();
              }
void IRQ(void)//中断函数
{
    
    
      a=1;
}

2) Los indicadores compartidos entre tareas en un entorno multitarea deben agregar volátiles
Como se explicó en el ejemplo anterior,
3) Registros de hardware mapeados en memoria, porque cada lectura y escritura en él puede tener diferentes significados.
Suponga que se va a inicializar un dispositivo y una cierta dirección de registro de este dispositivo es 0xff800000.

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

Después de la compilación, el compilador piensa que el ciclo anterior es inútil durante mucho tiempo y no tiene ningún efecto en el resultado final, porque al final solo asigna el puntero de salida a 4, por lo que el resultado del compilador es equivalente a:

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

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. Una vez optimizado 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, debe usarse volatile para informar al compilador que esta variable es inestable y no optimizar cuando se encuentre con esta variable.

volatile  int *output=(volatile  int *)0xff800000;

Las tres situaciones anteriores pueden protegerse utilizando estos tres métodos respectivamente:
1) Se puede realizar desactivando las interrupciones
2) La programación de tareas está prohibida
3) Solo puede depender de un buen diseño de hardware.
La pregunta es
1) ¿Puede un parámetro ser constante o volátil?
Sí, por ejemplo, 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.
En las variables modificadas por la palabra clave volatile se debe buscar la dirección de memoria correspondiente cada vez que se acceda a ellas, ya que pueden modificarse en cualquier momento. Modificado por const solo puede significar que esto no puede ser modificado por el programador, pero puede ser modificado por el sistema.
2) ¿Puede un puntero ser volátil?
Sí, cuando una subrutina de servicio intermedio modifica un puntero a un búfer.
4. La esencia de lo volátil:
1) 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; más tarde, cuando se recupera el valor de la variable, el valor es tomado directamente del registro; cuando se cambia el valor de la variable en este hilo, el nuevo valor de la variable se copiará al registro al mismo tiempo, para mantener la consistencia.

2) 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.

3) Cuando el valor de este registro cambia debido a otros subprocesos, etc., el valor de la variable original no cambiará, provocando que el valor leído por el programa de aplicación sea inconsistente con el valor real de la variable.
Da un ejemplo de nuevo

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

El propósito es devolver el cuadrado del valor al que apunta el puntero ptr. 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 (si no hay otros subprocesos o las condiciones externas cambian * ptr, entonces esta oración es correcta). Como resultado, es posible que este código no devuelva el valor cuadrado que esperaba. El código correcto es el siguiente:

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

Nota: Es probable que el uso frecuente de volatile aumente el tamaño del código y reduzca el rendimiento, por lo tanto, use volatile de manera razonable.

Resumen:
Las variables volátiles tienen dos funciones:
1) Decirle al compilador que no optimice;
2) Decirle al sistema que siempre tome la dirección de la variable de la memoria, en lugar de tomar el valor de la variable de la caché o del registro (con volatile y sin sistema volátil generará caché)

constante

Explicación La
palabra clave const se usa para definir constantes. Si una variable es modificada por const, su valor no se puede cambiar más. Puede proteger las cosas modificadas, prevenir modificaciones accidentales y mejorar la robustez del programa.
En comparación con las instrucciones precompiladas, const Los modificadores tienen las siguientes ventajas:
1) Las instrucciones precompiladas solo realizan un reemplazo simple de valores y no pueden realizar verificación de tipo Para
verificar:
mire const usado para modificar cadenas estáticas constantes, por ejemplo:
const char * str = "fdsafdsa";
si Sin la modificación const, podemos escribir str [4] = 'x' enunciado más tarde, intencionalmente o no, lo que resultará en la asignación del área de memoria de solo lectura, y luego el programa terminará inmediatamente de manera anormal. Este error se puede verificar cuando se compila, lo cual es la ventaja de usar const. Deje que se encuentren errores lógicos en tiempo de compilación.

2) Puede proteger las cosas modificadas, prevenir modificaciones accidentales y mejorar la robustez del programa.3
) El compilador generalmente no asigna espacio de almacenamiento para constantes const ordinarias, pero las guarda en la tabla de símbolos, lo que lo convierte en una compilación. Las constantes de tiempo, sin la operación de almacenar y leer la memoria, la hacen muy eficiente.
4) El alcance de la definición de macro se limita al archivo actual. Por defecto, los objetos const solo son válidos en archivos. Cuando las variables const con el mismo nombre aparecen en varios archivos, es equivalente a definir variables independientes en diferentes archivos. Si desea compartir objetos const entre varios archivos, debe agregar la palabra clave extern antes de la definición de la variable (tanto en la declaración como en la definición).
Variable modificada 1.const

const int n=1;
int const n=1;

Estas dos formas de escritura son iguales. Ambas significan que el valor de la variable n no se puede cambiar. Cabe señalar que al usar const para modificar la variable, se debe inicializar el cambio de cara, de lo contrario la asignación no será posible posteriormente. .
2. El puntero constante y la constante del puntero
se pueden expresar de la siguiente manera

const int * n;
int const * n;

1) El puntero constante dice que el valor de la variable no se puede cambiar a través de este puntero, pero el valor de la variable aún se puede cambiar a través de otras referencias.

int i=5;
const int* n=&i;
i=6;

El valor señalado por el puntero constante no se puede cambiar, pero esto no significa que el puntero en sí no se pueda cambiar, y el puntero constante puede apuntar a otras direcciones.

int a=5;
int b=6;
const int* n=&a;
n=&b;

2) La constante del puntero se refiere al puntero en sí es una constante, no puede apuntar a otras direcciones

int *const n;

Cabe señalar que la dirección a la que apunta la constante de puntero no se puede cambiar, pero el valor almacenado en la dirección se puede cambiar y se puede modificar con otros punteros a la dirección modificada. La clave para distinguir entre un puntero constante y un puntero constante es la posición del asterisco. Usamos el asterisco como línea divisoria. Si const está en el lado izquierdo del asterisco, es un puntero constante, y si const está a la derecha lado del asterisco, es una constante de puntero. Si leemos el asterisco como un "puntero" y la constante como una "constante", el contenido es exactamente el mismo.

int const*n;是常量指针
int*const n;是指针常量

Un puntero constante a una constante es una combinación de los dos anteriores. La posición a la que apunta el puntero no se puede cambiar y el valor de la variable no se puede cambiar a través de este puntero, pero el valor de la variable aún se puede cambiar a través de otros punteros.

const int* const p;

3)
Modificar los parámetros de la función 1) Evitar la modificación del contenido apuntado por el puntero
2) Evitar la modificación de la dirección apuntada por el puntero
3) Combinación de los dos anteriores.
4) Modificación del valor de retorno de una función
Si el valor de retorno de una función en el modo "transferencia de puntero" se modifica con const, el contenido del valor de retorno de la función (puntero) no se puede modificar y el valor de retorno solo se puede modificar. asignado al mismo con puntero de tipo modificado const.
5)
Modificar variables globales. El alcance de las variables globales es el archivo completo. Debemos tratar de evitar el uso de variables globales, porque una vez que una función cambia el valor de las variables globales, también afectará a otras funciones que hacen referencia a esta variable, resultando en la excepción de errores Es difícil encontrar que si debemos usar variables globales, debamos intentar usar modificadores const para modificar, para evitar modificaciones humanas innecesarias, el método utilizado es el mismo que el de las variables locales.
3. Algunas sugerencias
para usar const 1) Use const audazmente, lo que le traerá un sinfín de beneficios, pero la premisa es que debe averiguar la razón;
2) Para evitar los errores de asignación más generales, como la asignación de variables const, consulte la preguntas de pensamiento para obtener detalles;
3) El uso de const en parámetros debe usar referencias o punteros en lugar de instancias de objetos generales, por las mismas razones que las anteriores;
4) Los tres usos de const en funciones miembro (parámetros, valores de retorno, funciones) son muy importante Buen uso;
5) No establezca fácilmente el tipo de valor de retorno de la función como constante;
6) A excepción de los operadores sobrecargados, generalmente no establezca el tipo de valor de retorno como una referencia constante a un objeto;
7) Cualquier dato que no modificarse Todas las funciones miembro deben declararse como tipo constante.

Resumen de constante y volátil

1. Un objeto puede ser modificado por const y volátil al mismo tiempo, lo que indica que este objeto incorpora una semántica constante, pero al mismo tiempo puede ser modificado por circunstancias inesperadas en el contexto del programa donde se encuentra el objeto actual.
2. La variable modificada por la palabra clave volatile debe buscarse la dirección de memoria correspondiente cada vez que se accede a ella, ya que puede ser modificada en cualquier momento. Modificado por const solo puede significar que esto no puede ser modificado por el programador, pero puede ser modificado por el sistema.
3. const y volatile son modificadores de tipo, con una sintaxis similar, que se utilizan para declaraciones de parámetros de función o variable, y también pueden restringir funciones miembro no estáticas. La variable de tipo declarada con ella puede cambiarse por algunos factores desconocidos para el compilador, como el sistema operativo, el hardware u otros subprocesos. Al encontrar la variable declarada por esta palabra clave, el compilador ya no optimiza el código que accede a la variable, lo que puede proporcionar acceso estable a direcciones especiales y evitar que el compilador ajuste el orden de las instrucciones para operar variables volátiles. Cuando se requiere el valor de una variable declarada por volatile, el sistema siempre lee datos de la memoria donde se encuentra, incluso si la instrucción anterior acaba de leer datos de ella. Y los datos leídos se guardan de inmediato.
4. Puntero volátil: 1. Modifique el objeto al que apunta el puntero, y los datos son constantes o volátiles: const char * cpch; volatile char * vpch ;; 2. El valor del puntero en sí: una variable entera que representa la dirección , que es constante o volátil: char * const pchc; char * volatile vchc ;.

Supongo que te gusta

Origin blog.csdn.net/weixin_42271802/article/details/105973024
Recomendado
Clasificación