Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


3.2.1d volatile

§1 Sinopsis

Hemos señalado repetidas veces que una de las premisas de diseño del C++ es la velocidad de ejecución. En este sentido, los diseñadores de estos compiladores adoptan todas las estrategias posibles para garantizarlo. Por ejemplo, consideremos el siguiente trozo de código:

...
func (/* ... */);        // realiza determinado proceso

int limit = 1200;        // definición del límite para el bucle

for (int c=0; c<limit; c++) func(/* ... */);

...


El proceso definido por func se repite mientras que el contador c se mantiene inferior al límite limit definido en la sentencia anterior. Puesto que en la condición del for ( 4.10.3) no se altera el valor de la variable limit, el compilador puede asumir que este valor se mantiene constante durante la ejecución del bucle, y puede que sea almacenado como variable de registro ( 4.1.8b) a fin de ahorrarse lecturas y tenerla rápidamente accesible para la comparación c<limit,  es más que posible que c también sea declarada variable de registro, en cuyo caso las operaciones de incremento unitario c++, y comparación con limit, serían procesos muy rápidos.

El problema es que, en determinadas circunstancias, este tipo de asunciones no es conveniente. Por ejemplo, supongamos que el programa C++ al que corresponden las sentencias anteriores está controlando un sistema de instrumentación en el que la variable limit puede ser alterada por un dispositivo o proceso externo (puede ser otro hilo de ejecución del programa - 1.7-).

En tales casos nos encontramos en una situación justamente opuesta a las constantes ( 3.2.1c).  En aquel caso se indica al compilador: "Este valor se mantendrá inalterable a lo largo de la ejecución". En ocasiones como la descrita, necesitamos indicar al compilador justamente lo contrario: "No hacer ninguna suposición sobre este valor, incluso si se ha leído en la sentencia anterior".

Para conseguir esto, C++ dispone de un indicador específico; la palabra clave volatile es un modificador que añadido a una variable, advierte al compilador que esta puede ser modificada por una rutina en segundo plano (background), por una rutina de interrupción, o por un dispositivo de entrada salida. Es decir, que puede sufrir modificaciones fuera del control del programa.

La declaración de un objeto con este atributo, previene al compilador de hacer ninguna asunción sobre el valor del objeto mientras se ejecutan instrucciones en las que esté involucrado, advirtiéndolo que dicho valor puede cambiar en cualquier momento.  También previene al compilador de que no debe hacer de dicho objeto una variable de registro.

§2 Sintaxis

volatile [<tipo-de-variable>] <nombre-de-variable> [ = <valor> ];

<nombre-de-función> ( volatile <tipo> <nombre-de-variable> );

<nombre-de-metodo> volatile;

volatile <instanciado de clase>;

§3 Ejemplo

volatile int ticks;          // primer caso de la sintaxis.

void timer( ) { ticks++; }

void wait (int interval) {

  ticks = 0;

  while (ticks < interval);  // No hacer nada (esperar)

}

En este ejemplo, las rutinas implementan una espera de ciclos (ticks) especificados por el argumento interval (asumiendo que el reloj está asociado a un dispositivo hardware de interrupciones). Un compilador correctamente optimizado no debería cargar la variable ticks dentro de la rutina de comprobación del bucle while, dado que el bucle no cambia el valor de dicha variable.

Otros ejemplos:  4.9.3 9.1;

§4 volatile y const

Como se ha señalado, el especificador const puede coexistir con el atributo volatile ( 3.2.1c)

§5 volatile con punteros

Hay que hacer la salvedad de que, contra lo que ocurre con el especificador const, que crea un nuevo tipo, el atributo volatile solo realiza determinadas advertencias al compilador, manteniendo inalterado el tipo de variable sobre la que se aplica. Sin embargo, un puntero a-volatile-tipoX es considerado diferente de un puntero a-tipoX normal, del mismo modo que puntero a-tipoX es considerado distinto de puntero a-constante-tipoX.

Considere el siguiente ejemplo:

cons int x = 10;

int* xptr = &x;           // Error:

const int* xptr = &x;     // Ok.

 

volatile int y = 10;

int* yptr = &y;           // Error:

volatile int* yptr = &y;  // Ok.

 

int z;

volatile int* zptr = &z   // Error:

§6 volatile con miembros de clases

El atributo volatile puede ser utilizado con miembros de clases (propiedades y métodos), de forma parecida al modificador const con miembros de clase ( 3.2.1c). En efecto, puede ser utilizado de tres formas distintas:


§6.1
En el sentido tradicional del especificador.

Cuando se aplica a propiedades de clase, indicando que se trata de variables cuyo valor puede ser modificado desde fuera del programa. En el ejemplo que sigue podemos encontrar la primera y segunda formas de sintaxis:

class C {

  volatile int x;                  // declara x volatile (private).

  int f1(int x, volatile int y) {  // declara argumento y volatile

    return x + y + C::x;
  }
  ...

};


§6.2 Cuando se aplica a métodos de clase (tercer caso de la sintaxis). Ejemplo:

<nombre-de-metodo> volatile;

Si en la declaración de un método de clase se utiliza volatile, la función debe ser invocada por objetos también volatile (ver el siguiente caso). Este uso del atributo solo puede utilizarse con funciones-miembro de clase.

Ejemplo:

class C {
  int x;                      // Private por defecto
  int func(int i) volatile {  // declara func volatile

    x = x + i;

  }

  ...
};


§6.3 Cuando se aplica a instancias de clase.

Además del sentido estricto (variable que puede ser modificada desde fuera del programa), cuando se utiliza con miembros de clases, volatile es una característica añadida de seguridad. En efecto, los objetos volatile intentan usar las funciones miembro definidas a su vez como volatile.  Si un objeto volatile invoca una función miembro no-volatile, el compilador muestra un mensaje avisando de la circunstancia.

Ejemplo:

#include <iostream.h>

class C {
  int num; // privado por defecto
  public:
  C(int i = 0) { num = i; }       // constructor por defecto
  int func(int i) volatile {      // version volatile de func
    cout << "Funcion volatile" << endl;
    return num = i;               // modifica miembro no-volatile
  }
  int func(int i) {               // versión no-volatile de func
    cout << "Funcion no-volatile" << endl;
    return num = i;               // modifica miembro no-volatile
  }
  void f(int i) {                 // función no-volatile
    cout << "Funcion no-volatile llamada con " << i << endl;
  }
};

int main() {                  // =================================
  C c_nv;                     // Instancia-1 Utilizará funciones no-volatiles
  volatile C c_v;             // Instancia-2 Utilizará funciones volatiles

                              // cuarto caso de la sintaxis
  c_nv.func(1);               // invoca versión no-volatil de func
  c_nv.f(1);                  // Ok. f es función no-volatil
  c_v.func(1);                // invoca versión volatil de func
  c_v.f(2);                   // Aviso: objeto-volatil invoca función no-volatil
}

Salida:

Funcion no-volatile
Funcion no-volatile llamada con 1
Funcion volatile
Funcion no-volatile llamada con 2


Observe que la clase C tiene dos versiones de la misma función func, y que no se trata de un caso de polimorfismo, puesto que ambas tienen exactamente la misma definición. En ausencia del especificador volatile en una de ellas, el compilador hubiese dado un error: Multiple declaration for 'C::func(int)', pero la presencia de este modificador hace que el compilador considere que ambos métodos son distintos. La distinción de cual de las dos se utilizará en cada invocación de un objeto depende de que el propio objeto sea declarado volatile o no-volatile.

En el ejemplo se han creado dos instancias de la clase: Una normal, no-volatile c_nv, y otra volatile, c_v.  Vemos que la invocación c_nv.func(1); utiliza la versión no-volatile, mientras que la invocación c_v.func(1); utiliza la versión volatile.

También puede comprobarse que el compilador genera un mensaje de aviso en la penúltima línea: Non-volatile function C::f(int) called for volatile object in function main(), avisando que una función no-volátil (f) ha sido invocada por un objeto volátil (c_v).


§7 El atributo volatile puede ser reversible. C++ dispone de un operador específico que puede poner o quitar este atributo de un objeto ( 4.9.9a operador const_cast).