C Language
Calificadores de tipo
Buscar..
Observaciones
Los calificadores de tipo son las palabras clave que describen semánticas adicionales sobre un tipo. Son una parte integral de las firmas de tipo. Pueden aparecer tanto en el nivel superior de una declaración (que afecta directamente al identificador) como en los subniveles (relevantes solo para los indicadores, que afectan los valores apuntados a):
Palabra clave | Observaciones |
---|---|
const | Evita la mutación del objeto declarado (al aparecer en el nivel superior) o evita la mutación del valor apuntado (al lado de un subtipo de puntero). |
volatile | Informa al compilador que el objeto declarado (en el nivel superior) o el valor apuntado (en los subtipos de puntero) pueden cambiar su valor como resultado de condiciones externas, no solo como resultado del flujo de control del programa. |
restrict | Una sugerencia de optimización, relevante solo para los punteros. Declara la intención de que durante la vida útil del puntero, no se utilizarán otros punteros para acceder al mismo objeto apuntado. |
El ordenamiento de los calificadores de tipo con respecto a los especificadores de clase de almacenamiento ( static
, extern
, auto
, register
), modificadores de tipo (con signed
, unsigned
, short
, long
) y los especificadores de tipo ( int
, char
, double
, etc.) no se aplica, pero La buena práctica es ponerlos en el orden mencionado:
static const volatile unsigned long int a = 5; /* good practice */
unsigned volatile long static int const b = 5; /* bad practice */
Cualificaciones de primer nivel
/* "a" cannot be mutated by the program but can change as a result of external conditions */
const volatile int a = 5;
/* the const applies to array elements, i.e. "a[0]" cannot be mutated */
const int arr[] = { 1, 2, 3 };
/* for the lifetime of "ptr", no other pointer could point to the same "int" object */
int *restrict ptr;
Cualificaciones de subtipo de puntero
/* "s1" can be mutated, but "*s1" cannot */
const char *s1 = "Hello";
/* neither "s2" (because of top-level const) nor "*s2" can be mutated */
const char *const s2 = "World";
/* "*p" may change its value as a result of external conditions, "**p" and "p" cannot */
char *volatile *p;
/* "q", "*q" and "**q" may change their values as a result of external conditions */
volatile char *volatile *volatile q;
Variables no modificables (const)
const int a = 0; /* This variable is "unmodifiable", the compiler
should throw an error when this variable is changed */
int b = 0; /* This variable is modifiable */
b += 10; /* Changes the value of 'b' */
a += 10; /* Throws a compiler error */
La calificación const
solo significa que no tenemos el derecho de cambiar los datos. No significa que el valor no pueda cambiar a nuestras espaldas.
_Bool doIt(double const* a) {
double rememberA = *a;
// do something long and complicated that calls other functions
return rememberA == *a;
}
Durante la ejecución de las otras llamadas *a
podría haber cambiado, por lo que esta función puede devolver false
o true
.
Advertencia
Las variables con calificación const
aún podrían cambiarse utilizando los punteros:
const int a = 0;
int *a_ptr = (int*)&a; /* This conversion must be explicitly done with a cast */
*a_ptr += 10; /* This has undefined behavior */
printf("a = %d\n", a); /* May print: "a = 10" */
Pero hacerlo es un error que conduce a un comportamiento indefinido. La dificultad aquí es que esto puede comportarse como se espera en ejemplos simples como este, pero luego salen mal cuando el código crece.
Variables volátiles
La palabra clave volatile
le dice al compilador que el valor de la variable puede cambiar en cualquier momento como resultado de condiciones externas, no solo como resultado del flujo de control del programa.
El compilador no optimizará nada que tenga que ver con la variable volátil.
volatile int foo; /* Different ways to declare a volatile variable */
int volatile foo;
volatile uint8_t * pReg; /* Pointers to volatile variable */
uint8_t volatile * pReg;
Hay dos razones principales para usar variables volátiles:
- Para interactuar con el hardware que tiene registros de E / S asignados en memoria.
- Cuando se usan variables que se modifican fuera del flujo de control del programa (por ejemplo, en una rutina de servicio de interrupción)
Veamos este ejemplo:
int quit = false;
void main()
{
...
while (!quit) {
// Do something that does not modify the quit variable
}
...
}
void interrupt_handler(void)
{
quit = true;
}
Al compilador se le permite notar que el bucle while no modifica la variable quit
y convierte el bucle en un bucle while (true)
sin fin. Incluso si la variable quit
se establece en el controlador de señal para SIGINT
y SIGTERM
, el compilador no lo sabe.
Declarar quit
como volatile
indicará al compilador que no optimice el bucle y el problema se resolverá.
El mismo problema ocurre al acceder al hardware, como vemos en este ejemplo:
uint8_t * pReg = (uint8_t *) 0x1717;
// Wait for register to become non-zero
while (*pReg == 0) { } // Do something else
El comportamiento del optimizador es leer el valor de la variable una vez, no hay necesidad de volver a leerlo, ya que el valor siempre será el mismo. Así terminamos con un bucle infinito. Para forzar al compilador a hacer lo que queremos, modificamos la declaración a:
uint8_t volatile * pReg = (uint8_t volatile *) 0x1717;