2. 2. Apuntadores y memoria dinámica.
Objetivo: Al término de este apartado el estudiante habrá
comprendido qué son los apuntadores, las operaciones que se
pueden realizar con ellos y su uso en los arreglos.
Los participantes explorarán estrategias para la implementación de
algoritmos que manipulen la memoria de manera dinámica, a través de
apuntadores bajo la sintaxis de lenguaje C, así como para la
programación de aplicaciones orientadas a la eficiencia del
almacenamiento interno de la computadora.
3. 2. Apuntadores y memoria dinámica.
2.1 Apuntadores (punteros)
2.1.1 Definición
2.1.2 Operaciones
2.1.3 Apuntadores y arreglos
2.1.4 Arreglos de apuntadores
2.1.5 Apuntadores de tipo estructura
2.2 Memoria dinámica
2.2.1 Funciones para asignación de memoria
2.2.2 Reasignación de memoria
2.2.3 Arreglos dinámicos de una y dos dimensiones
4. “Comprender y usar correctamente los apuntadores es con
seguridad lo más complicado del lenguaje C, pero también se
trata de un mecanismo muy poderoso. Tan poderoso que un
simple puntero descontrolado puede provocar que el
programa se cuelgue irremediablemente o incluso que falle
todo el sistema.”
A.M. VOZMEDIANO
5. Apuntadores.
Definición:
En el lenguaje de programación C, un apuntador es una variable cuyo
contenido no es un dato, sino la dirección de memoria donde esta
almacenado un dato.
En lugar de contener directamente el valor de los datos, un apuntador
"apunta" a la ubicación de memoria donde se almacenan los datos en
el sistema.
Apuntador Variable X
5
6. Dentro de la memoria del ordenador, cada dato
almacenado ocupa una o más celdas contiguas de memoria.
El número de celdas de memoria requeridas para almacenar
un dato depende de su tipo.
Por ejemplo, un dato de tipo entero (int) puede ocupar 32
bits (es decir 4 bytes), mientras que un dato de tipo carácter
ocupa 8 bits ( es decir 1 byte).
La computadora almacena el dato en un espacio de la
memoria en una determinada dirección.
Un puntero no es más que una variable cuyo contenido, no
es un dato, sino la dirección de memoria donde está
almacenado un dato.
7. Ejemplo:
Imaginemos que miLetra es una variable de tipo
carácter y que, por tanto, el sistema le asigna un
byte para ser almacenado.
char miLetra;
miLetra=´A´;
Al ejecutar este código, el sistema asigna una celda
de memoria para el dato.
Supongamos que la celda asignada tiene la
dirección 1200.
Al hacer la asignación miLetra=´A´, el sistema
almacena en la celda 1200 el valor 65, que es el
código ASCII de la letra ´A´.
1197
1198
1199
1200 65
….
Un apuntador sería en este ejemplo
una variable que contendría el valor
1200, es decir la dirección donde se
aloja la variable miLetra
Dirección de
memoria Contenido
8. Antes de declarar un
apuntador veamos el operador
&
El operador &, u operador de
dirección, se utiliza para
obtener la dirección de
memoria de una variable.
// Uso del operador de dirección para
obtener la dirección de memoria
direccionDeLaVariable = &variable;
Del ejemplo anterior
direccionDeLaVariable = &miLetra;
Daría como resultado 1200 para la variable
direccionDeLaVariable
char a;
scanf ("%c",&a);
9. Por otro lado, se tiene el operador * u operador de
indirección
Se utiliza para acceder al valor almacenado en la
dirección de memoria a la que apunta un puntero
La declaración de un apuntador se realiza utilizando el
operador de asterisco *.
char *V
Declara que V es una variable apuntador a un Char.
Los valores permitidos siempre incluyen la dirección
especial 0 y un conjunto de enteros positivos que se
interpretan como direcciones en el sistema.
Usando el ejemplo
anterior podemos hacer
que el apuntador v,
“apunte” a la variable
miLetra
v = &miLetra;
miletra
V
10. Casos especiales
A = 0;
A = NULL; /* equivale a A = 0 */
A = &i; /* A esta refiriéndose a i, conteniendo la dirección de i */
En este caso nótese que cuando un apuntador en C apunta a la dirección de
memoria 0, se dice que el apuntador contiene el valor nulo. El valor nulo en C se
representa comúnmente como NULL.
Un apuntador nulo no apunta a ninguna ubicación válida de memoria.
11. #include <stdio.h>
int main() {
// Definir una variable de tipo char
char miLetra;
// Asignar la letra 'A' a la variable
miLetra = 'A’;
// Declarar un puntero que apunte a la variable miLetra
char *V = &miLetra;
// Imprimir el valor de la variable
printf("El valor de la variable es: %cn", miLetra);
// Imprimir el valor de la variable usando el puntero
printf("El valor de la variable a través del puntero es: %cn", *V);
return 0; }
¿Cuál es la diferencia
entre la primera
instrucción printf y la
segunda?
¿cuál es la diferencia
entre el operador & y el
operador *?
12. Recapitulando, los apuntadores (punteros) son variables que
almacenan direcciones de memoria.
Permiten manipular y acceder directamente a la memoria, lo que
brinda mayor flexibilidad y eficiencia en algunas operaciones.
13. Supongamos:
int x=15, y, *apuntador;
Entonces las dos proposiciones:
apuntador = &x;
y = *apuntador; /* le estamos asignando 15 a y */
Son equivalentes a:
y = *&x; /* precedencia de operadores */
que a su vez equivale a:
y = x;
El operador * es un operador unario, llamado operador indirección, que opera sólo
sobre una variable puntero. Las variables puntero pueden apuntar a direcciones
donde se almacene cualquier tipo de dato: enteros, flotantes, caracteres, cadenas,
arreglos, estructuras, etc..
15. Ejercicio 2.
#include<stdio.h>
int main(){
char *pt1;
char var_c='W';
pt1 = &var_c;
printf("nEl valor de contenido a donde apunta pt1: %c", *pt1);
*pt1= 'A';
printf("nEl valor de contenido a donde apunta pt1 despues de la nueva asignacion: %c", *pt1);
return 0;
}
17. Ejercicio 4.
#include<stdio.h>
int main(){
int *pt2;
int b=100;
pt2 = &b;
printf("nEl valor de contenido a donde apunta *pt2: %d", *pt2);
printf("nLa direccion en donde se almacena el puntero &pt2: %d", &pt2);
printf("nLo que tiene guardado el puntero pt2: %d", pt2);
printf("nLa dirección de la variable b es: %d", &b);
return 0;
}
18. Ejercicio 5.
#include<stdio.h>
int main(){
int *pt2;
int b=100;
pt2 = &b;
printf("nEl valor de contenido a donde apunta *pt2: %d", *pt2);
printf("nLa direccion en donde se almacena el puntero &pt2: %d", &pt2);
printf("nLo que tiene guardado el puntero pt2: %d", pt2);
printf("nLa dirección de la variable b es: %d", &b);
int *pt1;
pt1=pt2;
printf("nEl valor de contenido a donde apunta *pt1: %d", *pt1);
return 0;
}
20. Ejercicio 7.
#include<stdio.h>
int main(){
float v1,*qt1;
int v2, *qt2;
char v3, *qt3;
qt1=&v1;
qt2=&v2;
qt3=&v3;
printf("nDar un valor decimal: ");
scanf("%f", &(*qt1));
printf("nDar un valor entero: ");
scanf("%d", &(*qt2));
printf("nDar un caracter: ");
fflush(stdin);
scanf("%c", &(*qt3));
printf("nEl contenido de lo que apunta *qt1 es %.3f", *qt1);
printf("nEl contenido de lo que apunta *qt2 es %d", *qt2);
printf("nEl contenido de lo que apunta *qt3 es %c", *qt3);
return 0; }
23. Aritmética de punteros (operaciones)
Con las variables de tipo apuntador sólo se pueden hacer dos
operaciones aritméticas, sumar o restar a aun apuntador u número
entero, y restar dos punteros.
Las operaciones de suma y resta en punteros en C están relacionadas
con la manipulación de direcciones de memoria.
Se utilizan para desplazarse a través de un array (arreglo) o bloque de
memoria.
Es importante destacar que estas operaciones están limitadas a
punteros que apuntan a elementos de un mismo array, ya que se basan
en el tamaño del tipo de datos al que apunta el puntero.
24. Aritmética de punteros (sumar y restar)
Con las variables de tipo apuntador sólo se pueden hacer dos
operaciones aritméticas, sumar o restar a aun apuntador un número
entero.
El resultado de estas operaciones no es tan trivial como imaginamos
que ocurre con las sumas aritméticas a las que estamos acostumbrados.
Del ejemplo donde tenemos un apuntador, apuntando a la dirección
1200 si le sumamos un 1 a este apuntador, el resultado puede ser
1201… pero también puede ser 1202 ó 1204. Esto se debe a que el
resultado depende del tipo de dato al que apunte nuestro apuntador.
miletra
V
W
1200
V++
miletra
V
W
1201
25. Aritmética de punteros (sumar y restar)
La suma de un puntero con un valor
entero se utiliza para desplazarse a través
de un array o bloque de memoria.
Cuando sumas un valor entero n a un
puntero, el puntero se desplaza n *
sizeof(tipo_dato) bytes hacia adelante en
memoria, donde tipo_dato es el tipo de
dato al que apunta el puntero.
Es decir, dado que lo que se suma es
un número a la dirección donde
empieza un dato, el resultado
depende del tamaño del dato.
En el caso de un entero:
entero
V
100
1200
V++
entero
V
100
1204
26. Por ejemplo:
char *p;
p=p+5;
Supongamos que p apunta a la dirección de memoria 800. Como cada carácter
ocupa 1 byte, al incremento en 5 unidades, apuntará a la dirección 805.
int *p;
p=p+5;
En este caso bajo los mismos supuestos, al incremento en 5 unidades pasará a
apuntar a la dirección 820 ( suponiendo que cada entero ocupe 4 bytes)
A los punteros se les puede aplicar las operaciones de incremento (++) y
decremento (--), considerando el efecto que aplique por el tipo de dato del
puntero.
int *p;
p++;
p
W
800
805
p
1865
800
820
27. Ejercicio 10. Suma aritmética de punteros
#include<stdio.h>
#include<conio.h>
int main(){
char *ptr;
char x='Q';
;
ptr=&x;
printf("nDireccion del puntero ptr: %d", &ptr);
printf("nContenido a donde apunta el puntero ptr: %c", *ptr);
printf("nDireccion almacenada en el puntero ptr: %d", ptr);
printf("nDirección de x: %d", &x);
printf("nContenido de x: %c", x);
ptr++;
printf("nnDireccion del puntero ptr: %d", &ptr);
printf("nContenido a donde apunta el puntero ptr: %c", *ptr);
printf("nDireccion almacenada en el puntero ptr: %d", ptr);
printf("nDirección de x: %d", &x);
printf("nContenido de x: %c", x);
x
ptr
Q
x
ptr
Q
28. ptr--;
printf("nnDireccion del puntero ptr: %d", &ptr);
printf("nContenido a donde apunta el puntero ptr: %c", *ptr);
printf("nDireccion almacenada en el puntero ptr: %d", ptr);
printf("nDirección de x: %d", &x);
printf("nContenido de x: %c", x);
ptr+=2;
printf("nnDireccion del puntero ptr: %d", &ptr);
printf("nContenido a donde apunta el puntero ptr: %c", *ptr);
printf("nDireccion almacenada en el puntero ptr: %d", ptr);
printf("nDirección de x: %d", &x);
printf("nContenido de x: %c", x);
return 0;
}
x
ptr
Q
x
ptr
Q
29. x
ptr
Q
Dirección del puntero &ptr = 6553228
Contenido de donde apunta*ptr =Q
Dirección almacenada en ptr =6553227
Dirección de &x =6553227
Valor de x =Q
ptr++
x
ptr
Q
Dirección del puntero &ptr = 6553228
Contenido de donde apunta*ptr = i
Dirección almacenada en ptr =6553228
Dirección de &x =6553227
Valor de x =Q
Ptr-- x
ptr
Q
Ptr+=2 x
ptr
Q
Dirección del puntero &ptr =6553228
Contenido de donde apunta*ptr =Q
Dirección almacenada en ptr =6553227
Dirección de &x =6553227
Valor de x =Q
Dirección del puntero &ptr = 6553228
Contenido de donde apunta*ptr =@
Dirección almacenada en ptr =6553229
Dirección de &x =6553227
Valor de x =Q
30. Resta de dos punteros:
La resta de punteros se usa para saber cuantos elementos del tipo de dato
apuntado caben entre dos direcciones diferentes.
Supongamos:
int arreglo[15];
int dif;
int *p1, *p2;
p1 = &arreglo[4];
p2 = &arreglo[12];
dif = p2 – p1;
El puntero p1 apunta al quinto elemento del vector, y el puntero p2, al
decimotercero. Al restar los dos punteros obtendremos el valor de 8, que es el
numero de elementos de tipo int que pueden almacenarse entre las direcciones p1
y p2.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
Arreglo[15]
p1 p2
31. Ejercicio 11. Resta de punteros
#include <stdio.h>
int main() {
int arreglo[15] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
int dif;
int *p1 = &arreglo[4]; // Apunta al quinto elemento (índice 4)
int *p2 = &arreglo[12]; // Apunta al treceavo elemento (índice 12)
dif=p2-p1;
printf("La diferencia entre los punteros es: %tdn", dif);
return 0;
}
32. Ejercicio 12.- Operadores relacionales y punteros
#include<stdio.h>
#include<conio.h>
int main(){
double *pt1, *pt2;
double x=3.1416, y = 8.5;
pt1=&x;
pt2=&y; //pt1=pt2;
printf("nDireccion del puntero pt1: %d", &pt1);
printf("nContenido a donde apunta el puntero pt1: %.3lf", *pt1);
printf("nDireccion almacenada en el puntero pt1: %d", pt1);
getch();
printf("nnDireccion del puntero pt2: %d", &pt2);
printf("nContenido a donde apunta el puntero pt2: %.3lf", *pt2);
printf("nDireccion almacenada en el puntero pt2: %d", pt2);
if(pt1<pt2){
printf("nEl puntero pt1 apunta a una direccion mas baja que pt2"); }
else if(pt1>pt2){
printf("nEl puntero pt1 apunta a una direccion mas alta que pt2"); }
else{
printf("nEl puntero pt1 apunta a la misma direccion que pt2");
}
return 0;
}
33. Punteros a cadenas
include<stdio.h>
#include<conio.h>
int main(){
char *p1, cadena[20];
printf("nCadena: ");
scanf("%s", cadena);
p1=cadena;
printf("nLa cadena leida es: %s", cadena);
printf("nImprimir el elemento 2 de la cadena por indice: %c", cadena[1]);
printf("nImprimir el elemento 2 de la cadena: %c", p1[1]);
printf("nImprimir el elemento 2 de la cadena: %c", *p1++);
printf("nImprimir el elemento 2 de la cadena: %c", *p1++);
printf("nImprimir el elemento 2 de la cadena: %c", *p1++);
return 0;
}
34. Tarea: simular la función strcmp
strcmp(*S1,*S2)
S1: "Sandy“
S2: "Ana"
1 si s1 >s2
S1: "Sandy"
S2: "Sandy"
0 si s1==s2
S1: "Ana"
S2: "Sandy"
-1 si s1< s2
int a;
a=strcmp(s1,s2);
36. #include<stdio.h>
#include<conio.h>
#include<string.h>
int my_strcmp(char *s1, char *s2){
while (*s1 != '0' || *s2 != '0') {
if (*s1 < *s2) {
return -1; // s1 es menor que s2
} else if (*s1 > *s2) {
return 1; // s1 es mayor que s2
}
// Avanzar a los siguientes caracteres en ambas cadenas
s1++;
s2++;
}
return 0; // Las cadenas son iguales
}
37. Apuntadores y Arreglos (como se relacionan)
Un arreglo es una estructura de datos estática que
almacena una colección de elementos del mismo tipo
bajo un único nombre.
Cada elemento en el arreglo se accede mediante un
índice o una clave que indica su posición dentro del
arreglo.
Los arreglos son utilizados para almacenar datos de
manera estructurada y para facilitar el acceso y la
manipulación de conjuntos de datos relacionados.
La memoria
Elem
1
Elem
2
Elem
3
Elem
4
Elem
5
38. Arreglos (continuación)
Para definir y acceder a un arreglo unidimensional
se utiliza la sintaxis siguiente:
tipoDeDato nombreDelaVariable [numElementos];
La siguiente rutina en C define un arreglo de 5
posiciones
Llena cada posición con números 0 al 40
Los imprime
#include <stdio.h>
int main() {
// Definir un arreglo de 5 posiciones
int numeros[5];
// Llenar el arreglo con un bucle del 0 al 4
for (int i = 0; i < 5; ++i) {
numeros[i] = i * 10;
// Llenar con valores (0, 10, 20, 30, 40)
}
// Mostrar los valores almacenados en el arreglo
printf("Valores en el arreglo:n");
for (int i = 0; i < 5; ++i) {
printf("Posición %d: %dn", i, numeros[i]);
}
return 0;
}
39. Apuntadores y arreglos:
Los punteros y los arreglos tienen una relación muy estrecha, ya que el nombre de
un arreglo es en realidad un puntero al primer elemento de de ese arreglo.
Si x es un arreglo unidimensional, la dirección del primer elemento puede ser
expresada como:
&x[0] o simplemente como x.
La dirección del elemento i-ésimo se puede expresar como &x[i] o como (x+i)
En este caso la expresión (x+i) no es una operación aritmética convencional, sino
una operación con punteros.
40. Si,
&x[i] y (x+i) representan la dirección del i-ésimo elemento de x,
Entonces:
x[i] y *(x+i) representan el contenido de esa dirección, o lo que
es lo mismo el valor del i-ésimo elemento de x.
Hasta ahora hemos accedido a los valores de los arreglos de la forma x[i], pero ya
vimos que también podemos utilizarlos con punteros.
Nos puede resultar mas cómodo utilizar la forma x[i] para acceder ala i-ésimo
elemento, sin embargo, debemos considerar que la forma *(x+i) es mucho más
eficiente que x[i], porlo que suele preferirse cuando la velociad de ejecucón es un
factor determinante.
41. En estructuras de datos, una pila es una colección de elementos con dos operaciones
principales: PUSH and POP.
Estas operaciones siguen el principio de LIFO (Last In, First Out), lo que significa que
el último elemento que se inserta es el primero en ser retirado.
• Push (Empujar): Agrega un elemento a la pila.
• Pop (Retirar): Elimina el elemento más reciente de la pila.
La implementación de una pila puede realizarse
utilizando un arreglo (array) ó una lista enlazada.
PUSH POP
TOPE
N
INICIO
42. #define n 5
int pila[n];
int *p1, *tope;
main( ){
int value, opci;
tope = pila;
p1=pila;
PUSH POP
TOPE
INICIO
p1
PILA
43. do {
printf("nn 1. Insert en la pila (PUSH)");
printf("n 2. Extraer en la pila (POP)");
printf("n 3. Salir");
printf("nnElija opción: ");
scanf("%d",&opci);
switch(opci){
case 1:
printf("n Dar el elemento o ítem: ");
scanf("%d",&value);
push(value);
break;
case 2:
pop();
break;
case 3:
printf("n[FIN DE EJECUCIÓN]");
getch();
break;
default:
printf("nOpción incorrecta [Pulse tecla]");
getch();
}
} while (opci != 3);
}
44. void push(int i){
if(p1 >= tope+n) {
printf("n Posible desbordamiento de pila.nEl dato no fue insertado");
return;
}
*(p1)=i;
p1++;
imprimePila();
}
45. void pop(){
if(p1 == tope) {
printf("n La pila esta vacia. No hay datos que extraer");
return;
}
p1--;
printf("nSale el dato %d", *p1);
*p1=0;
imprimePila();
}
47. Ejercicio: Cual es el resultado de cada una de las siguientes asignaciones
int x[100],b,*pa,*pb;
x[50]=10;
pa=&x[50];
b = *pa+1;
b = *(pa+1);
pb = &x[10];
*pb = 0;
*pb += 2;
(*pb)--;.
x[0] = *pb--;
48. int x[100],b,*pa,*pb;
x[50]=10;
pa=&x[50];
b = *pa+1; //Esto es: Al valor que tiene el array de x[50] sumarle 1.
//Esto es igual a: b=x[50]+1; => Su valor seria igual a 11.
b = *(pa+1); //Esto primero pasa a la siguiente dirección de memoria y luego lo referencia
//El resultado es: b = x[51];
Programación II.-Revisión de apuntadores
49. pb = &x[10]; //al puntero pb se le asigna la dirección de x[10]
*pb = 0; //Al valor que tiene el puntero se le asigna 0
//Esto es igual que decir: x[10] = 0
*pb += 2; //El valor del puntero se incrementa en dos unidades, es decir x[10] += 2
(*pb)--; //El valor del puntero se decrementa en una unidad.
x[0] = *pb--; //A x[0] se le pasa el valor de x[10] y el puntero pb, pasa a apuntar
a x[9]
//recuerda, que -- es post-decremento, primero asignará y luego restará.
50. #include<stdio.h>
#include<conio.h>
typedef struct coordenada{
int x;
int y;
} coor;
int main(){
coor *q1;
coor w1;
q1=&w1;
q1->x = 100;
q1->y = 200;
printf("nq1->x|%d|", q1->x);
printf("nq1->y|%d|", q1->y);
return 1;
Apuntadores de tipo estructura: Ejercicio 14
x y
w1
q1
100 200
w1
q1
53. Ejercicio: Manejo de estructuras con apuntadores
Supongamos que estamos creando una aplicación para gestionar información de
estudiantes.
Define una estructura llamada Estudiante, que contenga la siguiente información:
Nombre del estudiante (cadena de caracteres)
Número de identificación del estudiante (entero)
Nota del estudiante (flotante)
Luego, crea una función llamada ActualizarNota que tome como parámetros un
puntero a una estructura Estudiante y una nueva nota, y actualice la nota del
estudiante utilizando el puntero.
Finalmente, en la función principal (main), crea un estudiante, muestra su información
original, llama a la función ActualizarNota para cambiar la nota y luego muestra la
información actualizada del estudiante.
54. #include <stdio.h>
#include <string.h>
// Definición de la estructura Estudiante
struct Estudiante {
char nombre[50];
int id;
float nota;
};
// Función para actualizar la nota del estudiante
void actualizarNota(struct Estudiante *estudiante, float nuevaNota) {
estudiante->nota = nuevaNota;
}
55. int main() {
// Crear un estudiante
struct Estudiante estudiante1;
// Inicializar la información del estudiante
strcpy(estudiante1.nombre, "Juan Perez");
estudiante1.id = 12345;
estudiante1.nota = 85.5;
// Mostrar información original del estudiante
printf("Informacion Original:n");
printf("Nombre: %sn", estudiante1.nombre);
printf("ID: %dn", estudiante1.id);
printf("Nota: %.2fn", estudiante1.nota);
// Actualizar la nota del estudiante
actualizarNota(&estudiante1, 92.0);
// Mostrar información actualizada del
estudiante
printf("nInformacion Actualizada:n");
printf("Nombre: %sn", estudiante1.nombre);
printf("ID: %dn", estudiante1.id);
printf("Nota: %.2fn", estudiante1.nota);
return 0;
}
56. Caso de negocio: Sistema de Registro de Estudiantes
Una institución educativa desea implementar un sistema de registro de
estudiantes. Cada estudiante tiene la siguiente información asociada: nombre,
edad, número de identificación y calificaciones de tres asignaturas, es necesario
utilizar apuntadores y estructuras de datos estáticas.
Requerimientos del Sistema:
• Permitir el registro de hasta 5 estudiantes.
• Mostrar la información de un estudiante específico.
• Calcular y mostrar el promedio de calificaciones de un estudiante.
• Mostrar la información de todos los estudiantes registrados.
• Identificar al estudiante con la calificación más alta en una asignatura
específica.
57. Caso de negocio: Sistema de Registro de Estudiantes
El menú debe mostrar las siguientes opciones:
Registrar un estudiante……………………………………………...1
Mostrar la información de un estudiante ( atreves del ID)……….2
Salir…………………………………………………………………….3
Ingrese su opción:
Tips: Se debe estar validando el numero de estudiantes permitidos.
58. Memoria Dinámica
Es el espacio en memoria que se crea al declarar
variables de cualquier tipo de dato [int,char,float...] o
derivados [struct, matrices, punteros...]). La memoria
que estas variables ocupan no puede cambiarse
durante la ejecución y tampoco puede ser liberada
manualmente.
Memoria Estática
Es memoria que se reserva en tiempo de ejecución.
Su principal ventaja frente a la estática, es que su
tamaño puede variar durante la ejecución del
programa. (En C, el programador es encargado de
liberar esta memoria cuando no la utilice más). El uso
de memoria dinámica es necesario cuando no se
sabe el numero exacto de datos/elementos a tratar.
Memoria Dinámica
59. Memoria dinámica vs estática, diferencias, ventajas y desventajas
La memoria reservada de forma dinámica suele estar alojada en el heap o
almacenamiento libre, y la memoria estática en el stack o pila.
La pila generalmente es una zona muy limitada. El heap, en cambio, en principio
podría estar limitado por la cantidad de memoria disponible durante la ejecución
del programa y el máximo de memoria que el sistema operativo permita
direccionar a un proceso.
La pila puede crecer de forma dinámica, pero esto depende del sistema operativo.
En cualquier caso, lo único que se puede asumir es que muy probablemente
dispondremos de menor espacio en la pila que en el heap.
Diferencias
Ventajas
La memoria dinámica se puede ir incrementando durante la ejecución del
programa, lo cual hace un uso mas eficiente de la memoria.
Desventajas
Una desventaja de la memoria dinámica es que es más difícil de manejar. La
memoria estática tiene una duración fija, que se reserva y libera de forma
automática. En contraste, la memoria dinámica se reserva de forma explícita y
continúa existiendo hasta que sea liberada, generalmente por parte del
programador.
60. Memoria dinámica.
Todos los objetos tienen un tiempo de vida, es decir, el tiempo durante el cual se garantiza
que el objeto exista.
En C, existen 3 tipos de duración: estática, automática y asignada. Las variables globales
y las variables locales declaradas con el especificador static tienen duración estática. Se
crean antes de que el programa inicie su ejecución y se destruyen cuando el programa
termina.
Las variables locales no static tienen duración automática. Se crean al entrar al bloque en el
que fueron declaradas y se destruyen al salir de ese bloque.
Por último duración asignada se refiere a los objetos cuya memoria se reserva de forma
dinámica. Esta memoria se crea y se debe liberar de forma explícita por parte del
programador.
La biblioteca estándar de C proporciona las funciones malloc, calloc y realloc y free, para el
manejo de memoria dinámica. Estas funciones están definidas en el archivo de cabecera
<stdlib.h>
61. Memoria dinámica.
malloc (Memory Allocation)
La función malloc reserva un bloque de memoria y devuelve un puntero void que debe ser
convertido al tipo de puntero deseado, el parámetro tamaño indica la cantidad de bytes que
se deben asignar en la memoria.
void *malloc(size_t tamaño);
Ejemplo:
char * ptr;
ptr = (char*)malloc(100);
free(ptr);
Se esta
especificando que
es un puntero y el
tipo de dato a usar
Tamaño de la
memoria a
reservar
62. Ejercicio 17.- Uso de malloc (ya no usamos variables)
#include<stdio.h>
#include<stdlib.h>
int main(){
int *ptr;
ptr= (int *) malloc(sizeof(int));
*ptr=1000;
printf("nptr->|%d|", *ptr);
free(ptr);
return 0;
}
1000
ptr
4 bytes
int main(){
float *ptr;
ptr= (float *) malloc(sizeof(float));
*ptr=1000;
printf("nptr->|%f|", *ptr);
free(ptr);
return 0;
}
63. #include <stdio.h>
#include <stdlib.h>
int main() {
int *arreglo;
arreglo = (int *)malloc(10 * sizeof(int));
if (arreglo == NULL) {
// La asignación de memoria ha fallado
printf("Error: No se pudo asignar memoria.n");
return 1; // Salir con código de error
}
// Utilizar el arreglo asignado
// Liberar la memoria cuando ya no sea necesaria
free(arreglo);
return 0;
}
arreglo
40 bytes
64. Memoria dinámica, uso de new.
El uso de malloc o new son dos formas diferentes de asignar memoria dinámicamente,
pero es importante destacar que new no es una función en C, sino que pertenece a
C++.
• malloc es una función de la biblioteca estándar de C (stdlib.h) que se utiliza para
asignar un bloque de memoria de un tamaño específico.
• Devuelve un puntero void al inicio del bloque de memoria asignado.
• Es necesario realizar un casting explícito al tipo de datos deseado al asignar
memoria con malloc.
• No inicializa la memoria asignada; el contenido es indeterminado.
• new es un operador en C++ que se utiliza para asignar memoria dinámicamente y
construir objetos.
• Devuelve un puntero al tipo de dato solicitado y automáticamente realiza el casting
necesario.
• Inicializa la memoria asignada llamando al constructor del tipo de dato.
• Especifica automáticamente el tamaño de la memoria según el tipo de datos.
70. #include<stdio.h>
#include<stdlib.h>
int main(){
int *q1, *q2, *aux;
q1=(int*)malloc(sizeof(int));
q2= (int *) malloc(sizeof(int));
aux = NULL;
*q1=100;
*q2=300;
printf("nq1->|%d|", *q1);
printf("nq2->|%d|", *q2);
printf("naux->NULL");
aux=q2;
q2=q1;
q1=aux;
printf("nnDespues del intercambio de ligasn");
printf("nq1->|%d|", *q1);
printf("nq2->|%d|", *q2);
printf("naux->|%d|", *aux);
free(q1);
free(q2);
free(aux);
Return 0;
}
71. Simulación de un nodo con estructuras.
#include<stdlib.h>
#include<stdio.h>
typedef struct nodo{
int entero;
char caracter;
} nod;
int main(){
nod *ptr;
ptr=(nod*)malloc(sizeof(nod));
ptr->entero=300;
ptr->caracter='M';
printf("nptr->|%d|%c|", ptr->entero,ptr->caracter);
printf("nSe ha liberado la memoria");
free(ptr);
return 0;
}
300
ptr
M
72. Lista ligada con estructuras.
#include<stdlib.h>
#include<stdio.h>
typedef struct nodo{
char x;
int y;
struct nodo *z;
} nodi;
int main(){
nodi *ptr;
ptr=(nodi*)malloc(sizeof(nodi));
ptr->x='a';
ptr->y=50;
ptr->z=(nodi*) malloc(sizeof(nodi));
ptr->z->x= 'b';
ptr->z->y= 60;
ptr->z->z= NULL;
printf("nptr->|%c|%d|->|%c|%d|", ptr->x,ptr->y, ptr->z->x,ptr->z->y);
printf("nSe ha liberado la memoria");
free(ptr->z);
free(ptr);
return 0;
}
a
ptr
50 a
x y z
b 60 a
x y z
null
73. Punteros a punteros:
#include<stdio.h>
int main(){
int n;
int *ptr1;
int **ptr2;
ptr1=&n;
ptr2=&ptr1;
**ptr2=15;
printf("n El valor de n es:%d“, n);
return 0;
}
ptr1
n
ptr2
74. Ejercicio: Punteros de tipo estructura.
ptr
nom
ptrA
ptrB
ptrC
Juan q
9.5 s
8 t
flo
ent
nom
Rocio q
10 s
15 t
flo
ent
75. #include <stdio.h>
#include<string.h>
#include<stdlib.h>
#define largo 25
typedef struct cadena{
char nom[largo];
struct cadena *q;
}cad;
typedef struct flotante{
float flo;
struct flotante *s;
}flota;
typedef struct entero{
int ent;
struct entero *t;
}enter;
typedef struct conjunto{
cad *ptrA;
flota *ptrB;
enter *ptrC;
}con;
nom
Juan q
9.5 s
flo
8 t
ent
ptrA
ptrB
ptrC
77. printf("nptr1->|");
printf("tptrA -> |%s| q|-> |%s|NULL|",ptr1->ptrA->nom,ptr1->ptrA->q->nom);
printf("n tptrB -> |%.1f| s|-> |%.1f|NULL|",ptr1->ptrB->flo,ptr1->ptrB->s->flo);
printf("n tptrC -> |%d| t|-> |%d|NULL|",ptr1->ptrC->ent,ptr1->ptrC->t->ent);
printf("n t|");
ptr1
nom
ptrA
ptrB
ptrC
Juan q
9.5 s
8 t
flo
ent
nom
Rocio q
10 s
15 t
flo
ent
78. printf("nSe ha liberado la memoria");
free(ptr1->ptrA->q);
free(ptr1->ptrA);
free(ptr1->ptrB->s);
free(ptr1->ptrB);
free(ptr1->ptrC->t);
free(ptr1->ptrC);
free(ptr1);
}
ptr1
nom
ptrA
ptrB
ptrC
Juan q
9.5 s
8 t
flo
ent
nom
Rocio q
10 s
15 t
flo
ent
79. Ejercicio: Punteros a estructuras conformada de 2 estructuras.
p
x[20]
p1
p2
Juan q
10.9 pt1
f i
x[20]
Rocio q
f i
3 20.6 pt1
4
f i
6.6 pt1
5
80. Ejercicio: Programar el escenario reservando memoria con malloc.
p
p1
p2
p3
i f
10 pt1
3.1416
i f
20 pt1
4.1416
x c
Programacion II
30.45
100
q
81. Punteros: Funciones y procedimientos.
#include<stdlib.h>
#include<stdio.h>
void procedimientoX(int *ptr){
*ptr +=2;
}
int * funcionY(){
int *qr= (int *) malloc(sizeof(int));
*qr=900;
return qr;
}
int main(){
int *p= (int *) malloc(sizeof(int));
*p=800;
printf("nValor original de *p es: %d", *p);
procedimientoX(p);
printf("nValor de *p despues de regresar del procedimientoX es: %d", *p);
free(p);
p=funcionY();
printf("nValor de *p despues de regresar de la funcionY es: %d", *p);
free(p);
return 0;
}
82. Reasignación de memoria (realloc):
La función realloc() cambia el tamaño del bloque de memoria reservado
previamente. La sintaxis es:
void *realloc(*ptr, nuevo_tamaño * bytes_tipo );
El argumento ptr apunta al principio del bloque de memoria asignado, mientras que
nuevo_tamaño indica el nuevo tamaño del bloque y bytes_tipo es el número de
bytes correspondiente al tipo de dato. El contenido permanecerá inalterado hasta
el mínimo entre el tamaño inicial y el nuevo; la memoria recién asignada quedará
sin inicializar.
Es importante verificar si ptr (el puntero) es NULL, en el caso afirmativo es
necesario notificar un error en la reasignación de memoria.
83. Reasignación de memoria (realloc):
La función realloc() puede mover la ubicación de almacenamiento del bloque
designado previamente, ya que todos los elementos que lo conforman el bloque de
almacenamiento reasignado deben permanecer contiguos.
Si no hay suficiente almacenamiento para crecer el tamaño, el bLoque no se
modifica quedando en su estado original y realloc() devuelve NULL.
char *texto;
texto=(char*) malloc(100 * sizeof(char));
texto=(char*) realloc(texto, 500*sizeof(char));
84. #include<stdlib.h>
#include<stdio.h>
int main(){
float numf;
int cuenta=0, i;
float *numerosLeidos=NULL;
float *mas_numeros=NULL;
do{
printf("nIngresa un dato de punto decimal (0 para finalizar): ");
scanf("%f", &numf);
if(cuenta == 0){
mas_numeros=(float *)malloc(sizeof(float));
printf("nSe uso malloc");
}
else{
mas_numeros= (float *)realloc(numerosLeidos, (cuenta+1)*sizeof(float));
printf("nSe uso realloc");
}
86. Asignación de memoria (calloc):
La función calloc() funciona de modo similar a malloc, pero además de
reservar memoria, inicializa a 0 la memoria reservada. Se usa comúnmente
para arreglos y matrices. Está definida de esta forma:
void *calloc(size_t nmemb, size_t size);
El parámetro nmemb indica el número de elementos a reservar, y size el
tamaño de cada elemento. El ejemplo anterior se podría reescribir con
calloc de esta forma:
88. int main(){
int **mat;
printf("nDar numero de renglones: ");
scanf("%d", &ren);
printf("nDar numero de columnas: ");
scanf("%d", &col);
mat=(int **) calloc (ren, sizeof(int*));
for(int i=0; i<ren; i++)
mat[i]=(int *) calloc(col, sizeof(int));
leearreglo(mat);
imprimearreglo(mat);
return 0;
}
En este programa, se utilizan ** en la
asignación de memoria porque estás
trabajando con un arreglo bidimensional
dinámico. Un arreglo bidimensional
dinámico se representa como un puntero a
punteros, y cada puntero individual apunta
a un arreglo unidimensional.
Cuando se asigna memoria dinámica para
un arreglo bidimensional, se requiere un
doble puntero para manejar tanto los
renglones como las columnas del arreglo.
89. Ejercicio: Manejo de arreglos dinámicos.
Escribe un programa en C que solicite al usuario un número entero n.
Luego, crea dinámicamente un arreglo de enteros de tamaño n, e inicialízalo con
valores ingresados por el usuario.
Después, encuentra el valor máximo y la posición en la que se encuentra en el
arreglo.
Finalmente, libera la memoria asignada.
90. Conclusión: ¿Para que nos sirven los apuntadores?
• Acceso directo a la memoria: Esto es útil para operaciones eficientes y para
trabajar con estructuras de datos complejas.
• Dinámica de asignación de memoria: Los punteros se utilizan para asignar y
liberar memoria dinámicamente durante la ejecución del programa.
• Funciones y paso por referencia: Los punteros permiten pasar parámetros por
referencia a funciones, lo que significa que la función puede modificar el
contenido de la memoria apuntada por el puntero. Esto es especialmente útil
cuando se trabaja con grandes conjuntos de datos o se quiere evitar la sobrecarga
de copiar datos.
• Manipulación de cadenas de caracteres: En C, las cadenas de caracteres son
esencialmente matrices de caracteres. Los punteros son ampliamente utilizados
para manipular y trabajar con cadenas de caracteres de manera eficiente.
91. • Estructuras de datos complejas: Los punteros permiten la implementación de
estructuras de datos más complejas, como listas enlazadas, árboles y grafos. Estas
estructuras de datos pueden ser gestionadas eficientemente utilizando punteros
para enlazar nodos o elementos.
• Arreglos dinámicos: Los punteros permiten la creación de arreglos dinámicos,
cuyo tamaño puede ser determinado en tiempo de ejecución. Esto es útil cuando
no se conoce el tamaño exacto del conjunto de datos antes de que el programa
se ejecute.
• Operaciones de bajo nivel: Los punteros proporcionan un nivel de abstracción
más bajo que otros tipos de datos en C, lo que permite realizar operaciones de
bajo nivel, como la manipulación directa de bits o la implementación de
estructuras de datos personalizadas.
Aunque los punteros en C brindan un control preciso sobre la memoria, también
pueden conducir a errores de programación como desreferenciación incorrecta o
fugas de memoria si no se manejan correctamente. Por lo tanto, se debe tener
cuidado al trabajar con punteros y seguir buenas prácticas de programación para
evitar problemas.