Herramientas de usuario

Herramientas del sitio


arreglos

¡Esta es una revisión vieja del documento!


Arreglos

En realidad C no posee verdaderos arreglos como ocurre en 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 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 es equivalente escribir *(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 con 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:

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.

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. 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 y por lo tanto se puede asignar al parámetro p en 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];
}

Compíle 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
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-pc-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.gentoo.org/>...
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. Luego observe lo que sucede cuando se ejecuta la instrucción a[i]= 0.

arreglos.1344009033.txt.gz · Última modificación: 2012/08/03 15:50 por lmateu