Ipol:Modulo2

From Wiki
Jump to: navigation, search

Punteros, arreglos y estructuras

Punteros

Los punteros son un tipo especial de variable que permite acceder al contenido de otras variables, arreglos o posiciones de memoria arbitrarias. También permiten el pasaje de parámetros por referencia.

  • Escribir una función intercambiar que recibe dos punteros a enteros e intercambie los valores de los enteros entre sí. Para verificar el correcto funcionamiento escribir un programa que imprime los enteros antes y después de invocar la función.
  • Intentar realizar la misma función pasando como parámetros los enteros.
Archivos: primer.c.

Punteros y arreglos

  • Escriba un programa que defina un array de enteros de tamaño 10. Recorra el array utilizando punteros, imprima en pantalla el valor de los enteros y su dirección de memoria. Verifique que el comportamiento sea coherente con el tamaño reportado por sizeof().
Salida:

> ./segundo Address Value Size (bytes) 0xbfc6dfe0 2 4 0xbfc6dfe4 3 4 0xbfc6dfe8 5 4 0xbfc6dfec 7 4 0xbfc6dff0 11 4 0xbfc6dff4 13 4 0xbfc6dff8 17 4 0xbfc6dffc 19 4 0xbfc6e000 23 4 0xbfc6e004 29 4

  • Realice nuevamente la parte anterior, pero con un array de chars. Compare los resultados.
Salida:

> ./segundo2 Address Value Size (bytes) 0xbf82d822 e 1 0xbf82d823 N 1 0xbf82d824 g 1 0xbf82d825 I 1 0xbf82d826 n 1 0xbf82d827 E 1 0xbf82d828 e 1 0xbf82d829 r 1 0xbf82d82a  : 1 0xbf82d82b ) 1

Archivos: segundo.c segundo2.c.

Arreglos y cadenas de carácteres

  • Escribir una función que reciba cadena de caracteres (arreglo de caracteres, tipo char, terminada en null) e imprima la misma omitiendo las vocales. Utilizar primero if-else y otra versión case-switch.
  • Escribir una función que reciba un puntero a un arreglo de enteros de tamaño MAX_ENTEROS y que imprima los números que sean mayores que el número en la posición anterior del array. Recorrer el arreglo utilizando un índice. En una segunda versión utilizar punteros para la recorrida.
Archivos: tercer.c tercer2.c.

Estructuras

  • Definir una estructura persona, que tenga los campos siguientes: peso (float), edad (int), sexo (char).
Solución:

typedef struct {

   float peso;
   int edad;
   char sexo;

} persona;

  • Investigar el tamaño de la estructura utilizando la función sizeof().
  • Defina una estructura estudiante, que tenga los mismos atributos que la anterior y agregue (al final) un atributo nota (char). Investigar el tamaño de la nueva estructura utilizando la función sizeof().
Solución:

typedef struct {

   float peso;
   int edad;
   char sexo;
   char nota;

} estudiante;

  • Defina una nueva estructura estudiante2, que tenga los mismos atributos que la anterior, pero en el siguiente orden: peso, sexo, edad, nota. Investigar el tamaño de la nueva estructura utilizando la función sizeof().
Solución:

typedef struct {

   float peso;
   char sexo;
   int edad;
   char nota;

} estudiante2;

Salida:

> ./cuarto Tamaño estructura persona: 12 bytes. Tamaño estructura estudiante: 12 bytes. Tamaño estructura estudiante2: 16 bytes.

Archivos: cuarto.c.

Introducción al DDD (Data Display Debugger)

Data Display Debugger o DDD (http://www.gnu.org/s/ddd/) es una popular interfaz gráfica de usuario para depuradores en línea de comandos como GDB, DBX, JDB, WDB, XDB, el depurador de Perl o el depurador de Python. DDD se rige bajo licencia GNU GPL y es software libre. Veremos algunos ejemplos de su uso con el ejecutable generado a partir del ejercicio anterior.

  • Para poder usar el debugger, se debe compilar con la opción -g (tomemos como ejemplo el archivo cuarto.c del ejercicio anterior):

> gcc -g cuarto.c -o cuarto

  • Ejecutar ahora el ddd (interfaz gráfica) para el archivo cuarto:

> ddd cuarto

Abre la siguiente ventana:
ddd_cuarto.png
  • Se debe poner un breakpoint para pausar la futura ejecución del programa, de modo de conservar las variables en memoria. Ésto puede hacerse de forma gráfica (haciendo doble click al comienzo de una línea del código), o ejecutando en la consola de gdb la siguiente instrucción (donde el número que se pasa como parámetro, 44, es la línea donde se quiere insertar el breakpoint):

(gdb) break cuarto.c:44

  • Por último, se corre el programa cargado, con lo cual estamos en posición de observar las variables y la memoria usada por el programa:

(gdb) run

  1. Observe el contenido de las diferentes variables involucradas en el programa. Utilizar el comando:

(gdb) graph display <variable>

  1. Nota: Para observar el contenido de todas las variables locales del programa ejecutar:

(gdb) graph display 'info locals'

  1. Cargue en ddd un programa que maneje punteros y arreglos (por ejemplo segundo.c de este módulo) y observe las diferentes variables del programa. Utilice las herramientas de display en gdb para derreferenciar los punteros.

Uniones

  • En el siguiente ejemplo se define la unión entre un Char y un Int. Examine el siguiente código e investigue su funcionamiento.

typedef union {

   unsigned char byte; 
   int entero; 

} registro

/* Ejemplo de uso */ registro reg1; reg1.entero = 50000; reg1.byte = 256; printf("reg1: 0x%X",reg1.byte);

  • Queremos implementar el acceso a los bits independientes de un registro. Para eso definimos un tipo de datos nuevo llamado tipo_bit. Utilice el siguiente código y verifique el acceso a los bits independientes. ¿Qué función cumple el separador ":"?. ¿En qué orden quedan almacenados los bits en memoria?.

typedef struct {

   unsigned char bit0 : 1; 
   unsigned char bit1 : 1; 
   unsigned char bit2 : 1; 
   unsigned char bit3 : 1; 
   unsigned char bit4 : 1; 
   unsigned char bit5 : 1; 
   unsigned char bit6 : 1; 
   unsigned char bit7 : 1; 

} tipo_bit;

  • Implemente un código similar al anterior que permita el acceso a los primeros cuatro bits (primer nibble) y a los siguientes cuatro bits (segundo nibble).
  • Investigar el tamaño de los tipos de datos definidos anteriormente.
  • Ahora queremos implementar un tipo de datos registro que permita acceder y modificar los datos de tres formas:
    1. byte
    2. nibble
    3. bit
Archivos: quinto.c quinto2.c.

Memoria dinámica

  • Memoria estática: Es el espacio de memoria que se crea al declarar variables, arrays o matrices de forma estática y cuyo tamaño no podemos modificar durante la ejecución del programa ni liberar el espacio que ocupa.
  • Memoria dinámica: Es memoria que se reserva en tiempo de ejecución. Su tamaño puede variar durante la ejecución del programa y puede ser liberado mediante la función free.

La bilbioteca estándar de C proporciona las funciones malloc, calloc, realloc y free para el manejo de memoria dinámica. Estas funciones están definidas en el archivo de cabecera stdlib.h.

malloc

La función malloc reserva un bloque de memoria y devuelve un puntero void al inicio de la misma. Definida como void *malloc(size_t size);, donde el parámetro size especifica el número de bytes a reservar. En caso de que no se pueda realizar la asignación, devuelve el valor nulo (NULL).

  • Ejemplo, reservar un espacio de memoria para un arreglo de 10 enteros:

int *a = malloc(sizeof(int) * 10);

calloc

La función calloc funciona de modo similar a malloc, pero además de reservar memoria, inicializa a cero la memoria reservada. Se usa comúnmente para arreglos y matrices. Está definida como void *calloc(size_t nmemb, size_t size); donde el parámetro nmemb indica el número de elementos a reservar, y size el tamaño de cada elemento.

  • Ejemplo, reservar un espacio de memoria para un arreglo de 10 enteros:

int *a = calloc(10, sizeof(int));

realloc

La función realloc redimensiona el espacio asignado de forma dinámica anteriormente a un puntero. Se define como void *realloc(void *ptr, size_t size); donde ptr es el puntero a redimensionar, y size el nuevo tamaño en bytes que tendrá. Si el puntero que se le pasa tiene el valor nulo, esta función actúa como malloc. Si la reasignación no se pudo hacer con éxito, devuelve un puntero nulo, dejando intacto el puntero que se pasa por parámetro.

Cuando se redimension la memoria con realloc, si el nuevo tamaño (parámetro size) es mayor que el anterior, se conservan todos los valores originales, quedando los bytes restantes sin inicializar. Si el nuevo tamaño es menor, se conservan los valores de los primeros size bytes. Los restantes también se dejan intactos, pero no son parte del bloque regresado por la función.

free

La función free sirve para liberar memoria que se asignó dinámicamente. Si el puntero es nulo, free no hace nada. Se define como void free(void *ptr); donde el parámetro ptr es el puntero a la memoria que se desea liberar.

  • Ejemplo:

int *a = malloc(sizeof(int) * 10); ... free(i)

Algunos consejos

  • Siempre que se reserve memoria de forma dinámica con malloc, realloc o calloc, se debe verificar que no haya habido errores (verificando que el puntero no sea NULL).
  • Si se van a usar las funciones de manejo de memoria dinámica, se recomienda siempre incluir siempre la librería stdlib.h.
  • Algunos programadores recomiendan convertir los punteros void que devuelven las funciones malloc, realloc y calloc al tipo de datos que se va a utilizar. Por ejemplo:

int *i = (int *)malloc(sizeof(int));

  • Liberar o tratar de utilizar un puntero que ya haya sido liberado usando free puede ser fuente de errores. Para evitar estos problemas, se recomienda que después de liberar un puntero siempre se establezca su valor a NULL. Por ejemplo:

int *i; i = malloc(sizeof(int)); ... free(i); i = NULL;

Más información en la página de Wikibooks acerca de memoria dinámica: http://es.wikibooks.org/wiki/Programaci%C3%B3n_en_C/Manejo_din%C3%A1mico_de_memoria.