Herramientas de usuario

Herramientas del sitio


variables

Variables

Supongamos que se declara la variable x como se muestra en la figura de abajo a la izquierda. El diagrama de la derecha nos permite definir los siguientes conceptos: variable, valor, identificador y tipo.

El identificador de una variable x es el nombre que se le da a esa variable, en este caso x. El valor es lo que almacena la variable en un instante dado, en este momento 5. Tipo es el conjunto al cual se restrigen los valores que puede almacenar la variable, en este caso int. Más específicamente un int está restringido en C a enteros comprendidos en el intervalo [-231, 231[.

Por último la variable es el área de memoria destinada a almacenar el valor y por lo tanto cuando hablemos de la variable x, no nos referimos a su identificador, si no que al espacio de memoria que ocupa. El identificador x es sólo un rótulo que permite distinguirla de otras variables.

Ejercicio

Haga un diagrama equivalente al anterior pero para la declaración:

double pi= 3.14159;

Tiempo de vida y alcance de una variable

Una variable tiene un tiempo de vida. La variable nace, es decir se crea cuando se le asigna un espacio en memoria. La variable muere, es decir se destruye cuando ese espacio es liberado para ser ocupado por otras variables.

El alcance de una variable es el o los trozos de código en donde su identificador es conocido. Típicamente un identificador es conocido solo a partir de la línea en donde se declara la variable. Pero hasta donde es válido usar el identificador varía según la clase de la variable. En C se distinguen 3 clases de variables según su tiempo de vida: globales, automáticas y dinámicas.

Variables globales

Son todas las variables que se declaran fuera de una función. Por ejemplo:

int v;
double w= 12;

int main() {
...
}

Una variable global se crea al momento de lanzar el programa (es decir cuando se crea el proceso) y solo se destruye cuando el programa termina (el proceso termina). Si a una variable global no se le da un valor inicial, como ocurre con la variable v más arriba, su valor inicial es 0, ya sea entera o punto flotante. Si se especifica un valor inicial debe ser una constante o una expresión calculable en tiempo de compilación. El compilador no genera instrucciones de máquina para calcular ese valor, si no que fabrica una imagen del contenido de todas la variables globales y graba esa imagen en el archivo ejecutable.

El alcance de una variable global comprende desde el punto del código en donde se declara hasta el final del archivo. Pero una variable global puede quedar oculta si se declara una variable automática con el mismo nombre.

variables automáticas

La variables automáticas son las variables que se declaran dentro de una función. También se llaman variables locales. Por ejemplo en el siguiente código:

double fact(int n) {
  double p= 1.0;
  while (n>1)  {
    p*= n;
    n--;
  }
  return p;
}

El parámetro n de la función fact y la variable local p son variables automáticas. El parámetro n se crea en el momento de invocar la función y se destruye al retornar la función (tiempo de vida). La variable p se crea cuando se ejecuta la instrucción que la declara y en este caso se destruye al retornar la función.

Existen casos más sutiles para el tiempo de vida de una variable automática como por ejemplo:

double sum_difs(double x[], double y[], int n) {
  double sum= 0.0;
  int i;
  for (i= 0; i<n; i++) {
    double max= x[i], min= y[i];
    if (max<min) {
      double t= min;
      min= max;
      max= t;
      /* Aquí se destruye t y termina su alcance */
    } 
    sum += max - min;
    /* Aquí se destruyen max y min, y termina su alcance */
  }
}

Al comienzo de una secuencia de instrucciones delimitadas por { }, se pueden declarar nuevas variables automáticas, como se observa con las variables max y t en el código de más arriba. El alcance de estas variables termina con la llave '}' que cierra la secuencia en donde se declararon. Son destruidas cuando la ejecución sale fuera del bloque en que se declaran.

Observe que variables declaradas al inicio de la función como x y sum tienen una sola reencarnación por cada invocación de sum_difs. En cambio max tendrá n reencarnaciones en total porque se crea y destruye una variable max por cada iteración del ciclo for. Por otra parte el número de reencarnaciones de t es variable. Si en una iteraciones no se cumple la condición del if interno, la variable t simplemente no se creará.

Inicialización

Es importante destacar que si la declaración de una variable automática no incluye un valor inicial, éste queda indeterminado y ¡rara vez resulta ser 0! Acceder al valor de una variable automática no inicializada es un error de programación en el 99.9% de los casos. Esto contrasta con la variables globales que al no ser inicializadas toman el valor 0 por omisión.

Ejercicio: uso incorrecto de variables

Estudie el siguiente programa y descubra qué es lo que hace supuestamente.

int sum_difs(double x[], int n) {
  int max= 0;
  int i;
  for (i= 0; i<n; i++) {
    int v;
    if (i != 0   &&   x[i]-v > max)
        max= x[i]-v;
    v= x[i];
  }
  return max;
}

El problema con este programa es que se usa la variable v sin haberle dado un valor inicial. El supuesto del programador es que v conserva su valor de una iteración a la otra. Pero esto constituye un error conceptual, aun cuando muchos compiladores no diagnostican el problema. De hecho, los compiladores no están obligados a entregar un error. Peor aún: ¡esto podría funcionar! Acá la creación y destrucción de la variable v es solo conceptual. Un compilador generaría un código muy ineficiente si efectivamente creara y destruyese la variable v en cada iteración del for. Cuando el compilador genera el código para una función, calcula cuál es espacio máximo que necesita para almacenar todas sus variables y asigna su memoria en la pila al inicio de la función. El costo de esta asignación de memoria es marginal: sumar una constante al puntero a la pila. Por lo tanto la declaración de variables dentro del cuerpo de un ciclo tiene costo 0.

Variables y funciones recursivas

Un caso más complicado se produce al considerar la versión recursiva de fact:

double fact(int n) {
  double res= 1;
  if (n>1) {
    res= n*fact(n-1);
  }
  return res;
}

Si se invoca fact(6) se invocará recursivamente fact(5), fact(4), fact(3), fact(2) y fact(1). Al comienzo de fact(6) se crea la primera reencarnación de res y vive hasta el retorno final de fact(6) a pesar de que su alcance queda oculto mientras se ejecuta fact(5). Esta última crea por su parte la segunda reencarnación de res y que vive hasta el retorno de fact(5). Y así se crean nuevas reencarnaciones de res en cada llamada recursiva de fact hasta que fact(1) crea la última reencarnación.

Variables dinámicas

La última categoría de variables corresponde a las variables dinámicas. Estas son variables anónimas, es decir no poseen un identificador. Se caracterizan porque se crean explícitamente invocando la función malloc y se destruyen también explícitamente con la función free. Para entenderlas es necesario estudiar los punteros.

variables.txt · Última modificación: 2014/08/06 13:20 por lmateu