Está en la página 1de 57

Algoritmos y Programación II

Memoria Dinámica

Escuela de Ingeniería Informática - Guayana


Prof. José Saad Khalil
Conceptos Generales

• Problema: Planteamiento de una situación cuya respuesta desconocida debe


obtenerse a través de métodos científicos
• Computador: En términos generales es un dispositivo electrónico para procesar
datos (programa Almacenado)
• Algoritmo: Conjunto de pasos a seguir para resolver un problema o llevar a
cabo una tarea.
• Lenguaje de Programación: Una notación para describir a la computadora lo
que se desea que haga (computaciones), en forma legible tanto para la maquina
como para los seres humanos.
• Programa: Conjunto de instrucciones para realizar una tarea en un computador
y esta escrito en algún lenguaje de programación.
• Abstracción: Capacidad de ocultar los detalles de tal forma que baste con
entender que se esta haciendo sin necesidad de comprender el como. A
menudo entrar en detalle tiende a crear mas dudas de tal forma que se obstruye
el proceso de creación de software.
Tipos de Lenguaje
Énfasis en el Cómo
Lenguajes Imperativos
Fortran, Algol, Basic, Pascal, C

Énfasis en el Quien
Programación Orientadas a Objetos
C++, Smalltalk, Java

Énfasis en el Qué
Lenguajes Declarativos
Programación Funcional
LISP, Scheme, Haskell
Programación Lógica
Prolog
Lenguaje C
Fue desarrollado originalmente por Dennis Ritchie y aparece
en el año 1972.

• Proposito general.
• Nivel Intermedio.
• No es fuertemente tipado.
• Alcance estático.
• Maneja una etapa de preprocesamiento para definición de macros e
inclusión de archivos.
• Soporta llamadas recursivas.
• Permite el uso de apuntadores.
• No soporta declaración anidada de funciones.
Lenguaje C

Proceso de construcción de una aplicación

.h .h .h .h

Preprocesamiento

.c .c

Compilación

.o .o

Enlace

Ejecutable
Lenguaje C
Estructura de un programa

Inclusion de Bibliotecas

Declaraciones globales

tipo función(parámetros){

declaraciones

secuencia de instrucciones

int main(){

secuencia de instrucciones

}
Lenguaje C
Ejemplo
Variables y tiempo de vida

Una variable es un elemento cuyo valor puede cambiar durante la ejecución. Esta
consta básicamente de una dirección de memoria y un espacio asignado a esta.

• El nombre o identificador es el símbolo que la denota.

• El tipo define lo que puede almacenar la variable y el conjunto de operaciones que


pueden aplicar.

• Un espacio en memoria puede ser reservado incluso sin una declaración formal de
variable.

Dirección

Valor
Variables y tiempo de vida

Otras definiciones importantes relacionadas con las variables, son:


• Binding o ligadura es el proceso de asociación de atributo (propiedades) a un
nombre (identificador), por ejemplo un nombre de variable con su
localización.

• Tiempo de vida de una variable, se refiere al intervalo de tiempo que trascurre


desde que se crea la variable hasta que se destruye.
• Alcance de una variable, es el conjunto de instrucciones en la que esa variable
es visible por medio de su identificador.

Es importante entender la diferencia entre alcance y tiempo de vida de una variable.


Si una variable no es visible, no necesariamente ha sido destruida. En cambio, si una
variable fue destruida, esa variable no es visible.
Distribución de la memoria

Pila (stack): Variables locales, parámetros de


funciones, direcciones de retorno, etc.

Montón (heap): Espacio de memoria que


puede emplearse de forma explícita mediante
las funciones de administración de memoria.

Variables Globales: Estas poseen un alcance


global en la aplicación y un tiempo de vida
igual al de la ejecución del programa.

Código del Programa: Conjunto de


instrucciones que conforman el programa.
Alcance de las variables
Tipos de datos
Los tipos de datos fundamentales en C son:

char caracter

int entero

float real

double real doble precisión

void sin tipo

Todos los demás tipos se basan en los anteriores. De igual forma se pueden emplear
los siguientes modificadores:

signed con signo

unsigned sin signo

short corto

long largo
Calificadores de acceso

Controlan las formas que se acceden o se modifican las variables.

const

No pueden ser modificadas por el programa, solo inicializar.

const int a=10;

volatile

El valor de una variable puede cambiar por medios no explícitamente


especificados por el programa.

const volatile char *puerto = (const volatile char *) 0x30;


Calificadores de clase de almacenamiento

Indican al compilador como deben almacenar las variables.

extern

Específica (declara) que un objeto esta declarado con enlace externo en otro
módulo (archivo) del programa, es decir esta definido en otro archivo.

int x,y; extern int x,y;


char c; extern char c;
int main(){ void func23(void){
x=123; y=10;
} }
Calificadores de clase de almacenamiento

static

Cuando es variable local se crea un almacenamiento permanente, con


alcance en el bloque que es declarada y cuando la variable es global se le
indica que solo es conocida en el archivo que se declara.

int veces(void){ int cuentas=0;


static int cuentas=0; int veces(void){
cuentas++; cuentas++;
return cuentas; return cuentas;
} }
Calificadores de clase de almacenamiento

register

Se le pide al compilador que mantenga el valor en un registro del CPU en


lugar de la memoria, donde normalmente se almacenan las variables, esto con
el fin de realizar operaciones mucho más rápido.

register int temp;


Estructuras
El lenguaje C proporciona varias formas para la creación de tipos de datos, a partir
de los tipos ya definidos.

En el caso de las estructuras estas representan una agrupación de miembros


diferentes que se referencian bajo un único nombre.
struct datos_persona{
unsigned int codigo;
char nombre[30];
char apellido[30];
char sexo;
};
Operaciones Permitidas
• Asignación (Siempre y cuando se defina con un typedef)

• Obtener su dirección con &

• Acceder a sus miembros


• Pasarla como argumentos a funciones y regresarlas de funciones (typedef)
Uniones y Campos de bits

Uniones: Una misma parte de la memoria es definida como dos o más tipos.

union numero{
int entero;
float real;
}

Campo Bits: tipo especial de estructura que permite el fácil acceso a bits
individuales.

struct estado{
unsigned: 4; (Campos sin nombre usados para padding)
unsigned cts:1;
unsigned drs:1;
}
Enumeraciones y Arreglos

Enumeraciones: Una lista de enteros con nombres.

enum meses {ene,feb,mar,abr,may,jun,jul,ago,sep,oct,nov,dic};

Arreglos: Colección de variables del mismo tipo que se referencia por un


nombre en común. Un elemento especifico se accede mediante un índice.

Tipo identificado[tamaño];

double balance[100];

balance[0] = 123.5;

Arreglos multidimensionales

tipo identificador [tamaño1] [tamaño2]… [tamañoN]


Apuntadores

En C se permite declarar una variable que contiene la dirección de otra variable; a


estas las denominaremos apuntadores. Cuando se declara un apuntador éste
contiene una dirección arbitraria, si leemos a dónde apunta nos dará un valor
indefinido y si se escribe en tal dirección estamos variando el contenido de una
posición de memoria que no conocemos por lo que podemos hacer que el sistema
tenga comportamientos no deseados, por lo que antes de hacer uso de un
apuntador debemos asignarle una dirección de memoria en nuestro espacio de
trabajo.

La forma de declarar un apuntador es la siguiente:

tipo *nombre;
Apuntadores

Los operadores empleados al momento de usar punteros suelen ser “*” para
acceder al contenido de una dirección de memoria y “&” para obtener la dirección
de un espacio de memoria.

p i

int i, *p; 10

i=10;

p=&i;
p i
*p = 5;
5
Aritmética de Apuntadores

Así como los apuntadores pueden apuntar a variables, puede darse el caso en
que un apuntador apunte a otro. Esta situación se denomina apuntador de
apuntador o indirección múltiple. Esta situación puede llevarse a la extensión
que se desee, pero son pocos los casos que lo requieran más allá de un
apuntador a un apuntador. /*
* UCAB Guayana
* Introduccion al Lenguaje C
*/
int i, *p, **q;
#include <stdio.h>
i=5; int main(int argc,char **argv){
int i=5,j=10,*p=&i,**pp=&p;
p=&i;
printf("valor de *p:%d\n", *p);
q=&p; printf("valor de **pp:%d\n", **pp);
p=&j; /* No se cambia pp pero ... */
printf("valor de **pp:%d\n", **pp);
q p i return 0;
5 }
/* --- Fin del programa --- */
Aritmética de Apuntadores

Operaciones Permitidas
• Incrementar o Decrementar cierta cantidad entera.
p
10 15 2 7 23 9

p = p+4
p++;
p-=2;

• Comparaciones.

i p q
i=p<q 1 10 15 2 7 23 9

• Restar dos punteros.


i
i=q-p 5
Cadenas de Caracteres (String)

Una cadena de caracteres (string) es un vector de caracteres que termina con un


carácter '\0', hay siempre que recordar que Las funciones que manipulan strings
esperan que los mismos terminen en '\0'.

Por ejemplo

"HOLA MUNDO"

H O L A M U N D O \0

En C hay la librería estándar con funciones para manejo de cadenas de caracteres


con el archivo de encabezado: string.h
Arreglos y Apuntadores

Existe una diferencia importante entre estas dos declaraciones:

char msg_a[] = “HOLA MUNDO” // Arreglo

char *msg_p = “HOLA MUNDO” // Apuntador

Veamos:

msg_a:

H O L A M U N D O \0

msg_p:

H O L A M U N D O \0
Arreglos y Apuntadores

Relación entre apuntadores y arreglos

Los apuntadores y los arreglos en C están relacionados íntimamente y puede ser utilizado
casi en forma indistinta. Un nombre de arreglo puede considerarse como un apuntador, los
apuntadores puede utilizarse para realizar cualquier operación que involucre arreglos.

Por ejemplo si se declara un arreglo de enteros int ia[5]; y un apuntador a entero int *iap;
Dado que el nombre del arreglo sin elemento es un apuntador al primer elemento se
puede hacer al apuntador igual a la dirección del primer elemento con iap=ia; (o más
extraño iap=&ia[0];) alternativamente el elemento ia[3] puede ser referenciado como

*(ia+3)

Existe una diferencia entre el nombre de un arreglo y un apuntador, un apuntador es una


variable, por eso iap++ es legal. Pero el nombre de un arreglo no es una variable por lo
que ia++ , por ejemplo, es ilegal.
Arreglos y Apuntadores

/*
* UCAB Guayana
* Introduccion al Lenguaje C
*/

#include <stdio.h>
int main(int argc,char *argv[]){
int *iap, ia[5]={0,1,2,3,4};

iap=ia;
printf("ia[3]:%d\n",ia[3]);
printf("*(iap+3):%d\n",*(iap+3));
printf("*(3+iap):%d\n",*(3+ia));
printf("3[ia]:%d\n",3[ia]);
return 0;
}
/* --- Fin ejemplo --- */
Apuntadores

El calificador const permite informarle al compilador que el valor de una variable


en particular no deberá ser modificado. Existen cuatro formas de declarar o pasar
un apuntador a una función:

✓ Un apuntador no constante a datos no constantes

char *s

✓ Un apuntador no constante a datos constantes

const char *s

✓ Un apuntador constante a datos no constantes

int * const ip

✓ Un apuntador constante a datos constantes

const int * const ip


Apuntadores

Existen punteros denominados punteros genéricos los cuales en lenguaje C son


conocidos como “puntero void” o bien void *.

Estos son empleados cuando deseamos manejar apuntadores cuyos tipo de dato
base son desconocidos o bien no tienen importancia para la acción que se desea
llevar a cabo.

void * permite especificar a una función un parámetro que pueda recibir


cualquier tipo de apuntador como argumento. También se utiliza para referirse a
la memoria en bruto (caso de la función malloc) cuando se desconoce el
significado de la memoria
Gestión de Memoria Dinámica

Existen 2 primitivas básicas que facilitan el manejo de memoria dinámica. Estas están
definidas en la biblioteca estándar <stdlib.h> y se conocen como malloc y free.

void *malloc(size_t numero_de_bytes)

Aloja (solicita al sistema) la cantidad de bytes solicitada y retorna un apuntador


genérico al espacio de memoria reservado. Si ocurre algún error al momento de
reservar la memoria el resultado retornado será un puntero nulo (NULL)

void free(void *p)

Libera (devuelve al sistema) la cantidad de bytes apuntados por p que fueron


previamente alojados mediante la función malloc. Hay que tener en cuenta que si
se usa free con un espacio previamente liberado o con un espacio de memoria que
no fue reservado por alguna primitiva para el manejo de memoria dinámica, los
resultados serán aleatorios.
Evaluación en Cortocircuito

En relación con los operadores lógicos /*


* UCAB Guayana
&& y || son evaluados de izquierda a * Introduccion al Lenguaje C
derecha y la evaluación se detiene tan */

pronto se conoce el resultado ya seas #include <stdio.h>


int main(int argc,char **argv){
falso en los and (&&) y verdadero en
int n,r;
los or (||), es decir se realiza una
for(n=-10;n<=10;n++)
evaluación en cortocircuito. if(n!=0 && (r=100/n))
printf("%3d %4d\n",n,r);
Una operación lógica (p.e. &&) se
return 0;
puede utilizar para proteger una }
/* --- Fin del programa --- */
operación potencialmente insegura en
una segunda expresión. Muchos de los
programas en C se basan en esa
propiedad.
Alias

Un Alias ocurre cuando el mismo elemento, por ejemplo una variable, está vinculado a
dos nombres diferentes al mismo tiempo. Esto puede ocurrir entre otras causas al hacer
uso de apuntadores.

El problema que presentan el alias son los efectos colaterales que producen, se define
como un efecto colateral de una instrucción como cualquier cambio en el valor de una
variable que persiste más allá de la ejecución de la instrucción, dichos efectos pueden ser
altamente dañinos y el uso de alias presenta dificultades para controlarlos.

int a = 5, *ap = &a;

*ap = 10;
printf("a: %d\n",a);
Referencias Pendientes

Una referencia pendiente es una localización que ha sido desasignada del entorno, pero a
la que el programa puede tener acceso todavía, es decir una localización puede ser
accedida más allá de su tiempo de vida.

char *i2a(int numero){


char buffer[20];
sprintf(buffer,"%d",numero);
return buffer;
}

En este caso la función ha retornado la dirección de buffer, pero el arreglo buffer tiene un
tiempo de vida igual a la ejecución de la función, es decir cuando se utilice la dirección
retornada por i2a luego de la llamada el espacio de buffer ya estará desasignado.
Referencias Pendientes

#include <stdio.h>
#include <stdlib.h>

int main(int argc,char **argv){


int *xp;

xp = (int *) malloc(sizeof(int));
*xp = 5;
free(xp);
printf("*xp: %d\n",*xp);
return 0;
}
/* --- Fin ejemplo --- */
Basura

En términos simples la memoria puede llenarse de información “basura” cuando no se


usa correctamente la memoria dinámica. Esto suele ocurrir cuando se presentan distintos
escenarios en los que el usuario solicita memoria al sistema pero olvida regresarla.

EJEMPLO 1
int *x;
x = (int *)malloc(sizeof(int));
x = NULL;

EJEMPLO 2
void p(void){
int *x;

x = (int *)malloc(sizeof(int));
*x = 2;
}
Paso por Referencia

El paso por referencia en lenguaje C se hace mediante el uso de apuntadores, de tal forma
que al pasar una referencia el método receptor puede alterar el valor de la variable cuyo
ámbito no es el mismo de la función.

void swap(int x, int y){ int i=2, j=3;


int t=x;
printf("%d-%d\n",i,j);
x=y; swap(&i,&j);
y=t; printf("%d-%d\n\n",i,j);
}

void swap(int *x, int *y){


int t=*x;

*x=*y;
*y=t;
}
Paso de Estructuras

En el caso de las estructuras, estas pueden pasarse por valor (donde no se puede
modificar el contenido de la misma fuera de la función) o bien por referencia (donde se
pasa la dirección de memoria de la misma).

void imprime(struct datos_persona ficha){


struct datos_persona{
printf("\nDatos de la Persona\n");
unsigned int codigo;
printf("\tC:%d\n",ficha.codigo);
char nombre[30];
printf("\tN:%s\n",ficha.nombre );
char apellido[30];
printf("\tA:%s\n",ficha.apellido);
int edad;
printf("\tS:%c\n",ficha.sexo);
};
}

void imprime(const struct datos_persona *fichap){


printf("\nDatos de la Persona\n");
printf("\tC:%d\n",fichap->codigo);
printf("\tN:%s\n",fichap->nombre );
printf("\tA:%s\n",fichap->apellido);
printf("\tS:%c\n",fichap->sexo);
}
Uso de Memoria Dinámica

int main(){

char *cp = abecedario(10);


char *abecedario(int n){
printf("Abecedario: %s\n",cp);
char *auxp,*restp, i;
free(cp);
return 0;
restp = auxp = (char*)malloc(n+1);
}
for(i=0;i<n;i++)
*auxp++='A'+i;
*(auxp)='\0';
return restp;
}

char *abecedario(int n){


char *restp, i;

restp = (char*)malloc(n+1);
for(i=0;i<n;i++) Abecedario: ABCDEFGHIJ
restp[i]='A'+i;
restp[i]='\0';
return restp;
}
Listas Simplemente Enlazadas

Una lista simplemente enlazada, es aquella en la que cada elemento de


información contiene un enlace al siguiente elemento, Cada elemento
generalmente consiste en una estructura que contiene información particular y un
apuntador que se utiliza como enlace. Para poder acceder a la lista se tiene un
apuntador al primero de la lista para poder acceder a ella.


Listas Simplemente Enlazadas

En ocasiones se trabaja con estructuras que tiene referencia a si misma.

typedef struct node {


char name[20]; name value next
int value;
struct node *next;
} Node;

Esta declaración recursiva de node podría parecer riesgosa, pero es correcta. Es ilegal
que una estructura contenga una instancia de si misma , pero
struct node *next;

Declara a next, como un apuntador a node, no como un node en si, a veces se


puede tener estructuras que se hagan referencia una a la otra.
Listas Simplemente Enlazadas

Insertar un nuevo elemento al frente

p Nuevo


Listas Simplemente Enlazadas

Insertar un nuevo elemento intermedio

p Nuevo

prev


Listas Simplemente Enlazadas

Insertar un nuevo elemento al final

p Nuevo


Listas Simplemente Enlazadas

Eliminar el primer elemento


p


Listas Simplemente Enlazadas

Eliminar un elemento intermedio


p

prev


Listas Simplemente Enlazadas

Eliminar un elemento intermedio


p

prev


Implementación
gestionlistas.h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct node { char name[20]; int value; struct node *next;} Node;

// New_item: Crea un nuevo elemento a partir del nombre y un valor


Node *new_item(char *name, int value);
// add_front: añade newp al frente de listp y retorna la nueva lista
Node *add_front(Node *listp, Node *newp);
// add_end añade newp al final de la listp y retorna la nueva lista
Node *add_end(Node *listp, Node *newp);
// insert: inserta newp ordenado en listp y retorna la nueva lista
Node *insert(Node *listp, Node *newp);
//lookup: busca un nombre en listp
Node *lookup(Node *listp, char *name);
// in_counter: cuenta los elementos en listp
int in_counter(Node *listp);
// print: muestra los elementos en listp
void print(Node *listp);
// free_all: libera todos los elementos de listp
Node *free_all(Node *listp);
// del_item: elimina la primera ocurrencia de name y retorna la nueva lista
Node *del_item(Node *listp, char *name);
// is_empty: retorna 1 si esta vacia 0 en caso contrario
int is_empty(Node *listp);
Implementación

gestionlistas.c
newp name value next

/*
* New_item: Crea un nuevo elemento a partir del nombre y un valor
*/

Node *new_item(char *name, int value){


Node *newp;

if((newp=(Node *)malloc(sizeof(Node)))==NULL){
fprintf(stderr,"new_item: error en malloc\n");
exit(1);
}
strcpy(newp->name,name);
newp->value = value;
newp->next=NULL;
return newp;
}
Implementación

gestionlistas.c
/*
* add_front: añade newp al frente de listp
* y retorna la nueva lista
*/

Node *add_front(Node *listp, Node *newp){


newp->next=listp;
return newp;
}

newp Node

listp

Implementación
/*
gestionlistas.c
* add_end añade newp al final de la listp
* y retorna la nueva lista
*/

Node *add_end(Node *listp, Node *newp){


Node *p;

if(listp==NULL)
return newp;
for(p=listp;p->next!=NULL;p=p->next);
p->next = newp;
return listp;
}
newp Node

listp

Implementación

gestionlistas.c

/*
* insert: inserta newp ordenado en listp y retorna la nueva lista
*/
Node *insert(Node *listp, Node *newp){
Node *p,*prev=NULL;

for(p=listp; p!=NULL && strcmp(p->name,newp->name)<0 ; p=p->next)


prev=p;
newp->next=p;
if(prev==NULL) //si el elemento iba al principio
return newp;
prev->next=newp;
return listp;
}
Implementación

/* gestionlistas.c
* lookup: busca un nombre en listp
*/
Node *lookup(Node *listp, char *name){
for(;listp!=NULL;listp=listp->next)
if(strcmp(name,listp->name)==0)
return listp;
return NULL; /* No se encontro */
}

/*
* in_counter: cuenta los elementos en listp
*/
int in_counter(Node *listp){
int n=0;

for( ; listp!=NULL ; listp=listp->next)


n++;
return n;
}
Implementación

gestionlistas.c

/*
* free_all: libera todos los elementos de listp
*/
Node *free_all(Node *listp){
Node *next;

for( ; listp!=NULL ; listp=next){


next = listp->next;
free(listp);
}
return NULL;
}
Implementación

gestionlistas.c
/*
* del_item: elimina la primera ocurrencia de name
*/
Node *del_item(Node *listp, char *name){
Node *p, *prev = NULL;

for(p=listp;p!=NULL;p=p->next){
if(strcmp(p->name,name)==0){
if(prev==NULL)
listp=p->next;
else
prev->next=p->next;
free(p);
return listp;
}
prev=p;
}
return listp; /* name no esta en la lista */
}
Implementación

gestionlistas.c
/*
* print: muestra los elementos en listp
*/
void print(Node *listp){
printf("-->");
for(;listp!=NULL;listp=listp->next)
printf("%s:%d-->",listp->name,listp->value);
printf("NULL\n");
}

/*
* is_empty: retorna 1 si esta vacia 0 en caso
contrario
*/
int is_empty(Node *listp){
return listp == NULL;
}
Ejemplo de Uso

#include "gestionalistas.h"

int main(){

Node *listp = NULL;

listp = insert(listp, new_item("Jose Saad", 7));


listp = insert(listp, new_item("Daniela Perez", 23));
listp = insert(listp, new_item("Mireya Orta", 17));
listp = insert(listp, new_item("Miguel Alvarez", 12));
print(listp);
listp = del_item(listp,"Jose Saad");
print(listp);

return 0;

Salida
-->Daniela Perez:23-->Jose Saad:7-->Miguel Alvarez:12-->Mireya Orta:17-->NULL
-->Daniela Perez:23-->Miguel Alvarez:12-->Mireya Orta:17-->NULL
Ejercicios
Dada una lista de elementos y las operaciones: primero, cola, insertar un elemento
por el frente e insertar un al final, vacio: Escriba los algoritmos en pseudo código,
para:

1. Calcular la longitud de la lista

2. Verificar si dos listas son iguales

3. Concatenar dos listas

4. Buscar un elemento de forma recursiva.

5. Ordenar la lista de forma recursiva.

6. Voltear (invertir) la lista de forma iterativa.

7. Voltear (invertir) la lista de forma recursiva.


Ahora utilizado las definiciones utilizadas, se desea que usted implemente en lenguaje C:

1. La función reverse, versión iterativa

2. La función reverse, versión recursiva

También podría gustarte