Herramientas de usuario

Herramientas del sitio


arreglos

Arreglos

En realidad C no posee verdaderos arreglos como los de Java. En C un arreglo de n elementos de un tipo T corresponde a un puntero al comienzo de un área de memoria que contiene n variables consecutivas de tipo T. De modo que lo que se vió en el capítulo sobre punteros es el 90% de lo que se puede escribir sobre arreglos.

El otro 10% corresponde a la declaración de arreglos de tamaño constante. La idea con estos arreglos es evitar tener que llamar a malloc para ubicarlos en el heap. El espacio en memoria se asigna ya sea en el área de variables globales, la pila o el heap según el contexto en que se declaran.

Arreglos globales

Estos se declaran fuera de toda función como las variables globales:

  double a[100];
  int b[4]= {1, 2, 4, 8};
  short c[]= {10, 20, 30};
  char s[]= {'h', 'o', 'l', 'a', 0};
  int main() {
    int i;
    a[0]= 0;
    a[1]= 1;
    for (i= 2; i<100; i++)
      a[i]= a[i-1] + a[i-2];
  }

El primero es un arreglo de 100 elementos inicializados por omisión en 0. El tipo de la expresión a es double* y corresponde a la dirección del primer elemento de un área de memoria global con espacio para 100 variables consecutivas de tipo double. Su tiempo de vida y alcance es el mismo de todas las variables globales, es decir su memoria se asigna al iniciarse el proceso y solo se libera al terminar el proceso. No se le asigna espacio con malloc. Se accede a sus elementos con la notación p[i] o bien *(p+i). Es decir el cuerpo del ciclo for es equivalente a *(a+i)= *(a-i-1) + *(a-i-2);.

En este caso a no es una variable. Es un error asignar una nueva dirección a a como en:

a= (double*)malloc(100*sizeof(double)); /* Esto es un error */

En este caso el identificador a representa una constante correspondiente a la dirección del primer elemento del arreglo en memoria y por eso el compilador reclama porque no se puede cambiar una constante. Esto queda representado en la siguiente figura:

Volviendo al código de arriba, el arreglo b corresponde a un arreglo de 4 elementos preinicializados con constantes conocidas en tiempo de compilación. El arreglo c también es un arreglo preinicializado solo que el compilador infiere el tamaño del arreglo contando la cantidad de elementos que aparecen en las llaves, en este caso 3. Por último el arreglo s corresponde al mismo caso que c. En la próxima sección veremos que s es en realidad un string.

Nótese que el compilador no genera ningún código para inicializar los arreglos globales. Los valores iniciales van directamente en el archivo binario que genera el compilador. Por eso es importante que los valores iniciales sean valores constantes o expresiones constantes, es decir que el compilador puede evaluar.

Inicialización de punteros globales

Las siguientes declaraciones globales son válidas:

int x, y;
int *p= &x;
int *q[]= {&x, &y};

En este caso &x y &y se considera constantes. En realidad el compilador no puede determinar sus valores pero a la larga sí se puede determinar esa dirección antes de comenzar la ejecución del programa. Existen mecanismos especiales en el compilador y en el linker para poder dar un valor inicial correcto en este caso.

Arreglos automáticos

También es posible declarar un arreglo local a una función. El tiempo de vida y alcance es el mismo de todas las variables automáticas. Se ubican en la pila del proceso. Por ejemplo:

void ordenar(int *p, int n) {
  ...
}

int f(int *p) {
  int a[20], i, s= 0;
  for (i= 0; i<20; i++)
    a[i]= p[i];
  ordenar(a, 20); /* ordenar recibe 'a' como puntero */
  for (i= 1; i<20; i++)
    s+= a[i]-a[i-1];
}

Acá a es un arreglo de 20 elementos que es creado al inicio de f y destruidos en el retorno de f. Cuidado, una función no debe retornar la dirección de un arreglo local. Aunque se puede y C lo permite, como su espacio es reutilizado en nuevas invocaciones de funciones, el resultado es impredecible y casi siempre erróneo.

Observe aquí como se usa el arreglo en donde corresponde usar un puntero. Esto es legal porque a representa la dirección de un entero, es decir su tipo es int* y por lo tanto se puede asignar al parámetro p de la función ordenar.

Arreglos locales preinicializados

También es posible inicializar un arreglo local. Pero a diferencia de los arreglos globales, los locales sí se pueden inicializar con valores no constantes. Por ejemplo:

double f(double x) {
  double a[]= { x, x*x, x*x*x };
  ...
}

El compilador genera código de máquina para evaluar las 3 expresiones.

Ejercicio

Edite el archivo loop.c con el siguiente código:

int main() {
  int i, j, a[10];
  for (i= 0; i<=10; i++)
    a[i]= 0;
  return a[5];
}

Compile y ejecute con:

% gcc -m32 loop.c
% ./a.out

La opción -m32 especifica que se genere código para x86. De otro modo en plataformas de 64 bits se genera código de 64 bits con resultados diferentes. Es importante no usar la opción -O de gcc que optimiza el código, lo cual también genera resultados diferentes.

  • ¿Por qué pasa lo que pasa?
  • Edite el programa y borre la variable j y vuelva a compilar y ejecutar

Vuelva a agregar la variable j exactamente como en el original y recompile nuevamente usando la opción -g para incluir información de debugging. Invoque gdb para depurar el programa como se indica a continuación:

% gcc -m32 -g bug.c
% gdb a.out
GNU gdb (Gentoo 7.3.1 p2) 7.3.1
...
Reading symbols from /u/i/lmateu/Test/a.out...done.
(gdb) b main
Breakpoint 1 at 0x80483ba: file bug.c, line 4.
(gdb) run
Starting program: /u/i/lmateu/Test/a.out

Breakpoint 1, main () at bug.c:4
4         for (i= 0; i<=10; i++)
(gdb) n
5           a[i]= 0;
(gdb) n
4         for (i= 0; i<=10; i++)
(gdb) n
5           a[i]= 0;
(gdb) n
4         for (i= 0; i<=10; i++)
(gdb) n
5           a[i]= 0;
(gdb) n
4         for (i= 0; i<=10; i++)
(gdb) n
5           a[i]= 0;
(gdb) n
4         for (i= 0; i<=10; i++)
(gdb) n
5           a[i]= 0;
(gdb) n
4         for (i= 0; i<=10; i++)
(gdb) p i
$1 = 4

Continúe así hasta que i tome el valor 10. Ejecute una iteración más. ¿Qué valor toma la variable i?

Arreglos dinámicos

Son todos los arreglos que se crean llamando a malloc y se destruyen explícitamente con free, como se vió en el capítulo sobre punteros. Por lo tanto se ubican en el heap. Su valor inicial es indeterminado y por lo tanto deben ser inicializados por el programador despúes de su creación.

Observe que Java no posee arreglos globales ni locales a una función. Todos los arreglos de Java son dinámicos.

arreglos.txt · Última modificación: 2016/09/27 10:02 por lmateu