Pro2324 Help

Tema 2 - Gestión dinámica de memoria

Pásame El Código

Visita nuestra nueva página web con contenidos actualizados y mucho más.

Estos apuntes también están en: Pásame el Código

    Organización de la memoria de un programa

    Existen dos lugares en memoria para almacenar elementos: la pila (stack) y el montículo (heap).

    Nombre

    Función

    Código

    El código compilado.

    Datos estáticos

    Datos estáticos compilados.

    Pila (stack)

    Se utiliza para la llamada de funciones y punteros, ahí se almacenan sus direcciones, parámetros, constantes locales, etc...

    ...

    Espacio para crecer.

    Montículo (heap)

    Parte de la memoria que no está ligada a lo guardado en la pila, se utiliza para las variables dinámicas, la memoria se reserva cuando se solicita (malloc).

    Definición de variables de tipo puntero

    Punteros en C
    • Un puntero es un tipo básico en C (como int, bool, float, etc.) y como tal ocupa en memoria una cantidad determinada (generalmente 4 bytes).

    • Una variable de tipo puntero contiene una dirección de memoria en la cual se almacena **una variable de otro tipo **.

    • Las variables de tipo puntero en C se declara para que apunte a un tipo particular de datos (int, float, etc.) y no puede apuntar a ningún otro.

    Sintaxis
    // Estructura typedef tipoApuntado* tipoPuntero // Otro ejemplo typedef int* tPEntero // tPEntero es un puntero a int tPEntero P // Has declarado un puntero P // que no apunta a nada todavía

    Reserva y destrucción dinámica de memoria

    Inicialización de punteros

    typedef int* tPEntero; tPEntero P1; int* P2;
    • tPEntero las variables declaradas con este nuevo tipo contendrán direcciones de memoria de **variables enteras **.

    • P1 se reserva memoria en la pila para guardar una dirección de memoria (4 bytes) que apuntara a un entero del montículo.

    • No es necesario definir un tipo para declarar un entero (como se ve en int* P2), pero se recomienda hacerlo de esta manera para aumentar la abstracción.

    • La memoria es reservada, pero no se elimina los contenidos que ya hubiera en esa zona. Inicialmente, P1 o P2 contendrá un valor basura.

      Utilizar punteros con contenido basura puede tener efectos fatales. Por lo tanto, es mejor inicializarlos a nulo P = NULL;

    La variable dinámica

    Creación de una variable dinámica

    Para crear la variable dinámica apuntada por un puntero se utiliza el operador malloc definido en el archivo de cabecera stdlib.

    #include <stdlib> P = malloc(sizeof(int)); // Memoria suficiente para un entero

    Memoria

    Heap

    Stack

    P

    int = *P

    Unsupported markdown: hr

    3F5000AC o NULL

    Destrucción de una variable dinámica

    Para liberar la memoria de la variable dinámica se utiliza free definido en el archivo de cabecera stdlib.

    Se marcará la variable a la que apunta el puntero como liberada, aunque la información no es destruida, ya no es accesible.

    #include <stdlib> free(P);

    Memoria

    Heap

    Stack

    P

    X

    Memoria liberada

    Unsupported markdown: hr

    3F5000AC o NULL

    Acceso a una variable dinámica

    // Declaración int i; int* p; // Creación /* - - - */ p = malloc(sizeof(int)) // Escritura i = 5; *p = 5; // Lectura j = i; j = *p;

    Asignación y comparación de punteros

    Asignación de valores a punteros

    // Tenemos dos variables dinámicas P y Q P = NULL; // Puntero a valor nulo P = malloc(sizeof(int)); // Se reserva la memoria (se le asigna dirección de memoria) *P = 3; // Se le asigna un entero // Q tenía un valor previo *Q = 4; P = Q; // Asignamos al puntero el valor de otro puntero *P = 11; printf("%d",*Q); // Resultado: 11

    Es decir ahora P no tiene la dirección de memoria que se le asignó al liberar la memoria, tiene la de Q. Visualmente:

    Heap

    Stack

    11

    P

    Q

    P = NULL;

    Heap

    Stack

    11

    P

    Q

    __

    P = malloc(...);

    Heap

    Stack

    3

    4

    P

    Q

    *P = 3;

    Heap

    Stack

    3

    4

    P

    Q

    P = Q;

    Heap

    Stack

    3

    11

    P

    Q

    *P = 11;

    Comparación de punteros

    // Identidad printf(P == Q); // Solo si tienen la misma dirección de memoria // Igualdad printf(*P == *Q); // Solo si su contenido es el mismo

    Q

    P

    *P *Q

    11

    3F5000AC

    3F5000AC

    Identidad

    Q

    P

    *Q

    11

    *P

    11

    3F5000AC

    600A232B

    Igualdad

    Resumen sobre el uso de punteros y variables dinámicas

    Definición y declaración
    typedef int* pEntero; pEntero p;
    Inicialización / Creación
    p = NULL; // O para las Variables dinámica p = malloc(sizeof(int));

    Se recomienda inicializar siempre los punteros, para no dejar valores basura.

    Destrucción de una variable
    free(p); // Liberar memoria (borrar valor) p = NULL;

    Siempre se debe hacer para crear un programa más eficiente en memoria.

    Contenido de la variable dinámica
    *p = valor; // Cambiar el valor variable = *p; // Copiar el valor
    Asignación de valores a punteros
    p = NULL; // Dirección nula p = malloc(sizeof()); // Dirección de memoria recién liberada p = q; // Dirección de otro puntero
    Comparación de punteros
    p == q; p != q; // Comparación entre punteros p == NULL; p != NULL; // Comparación con valor nulo

    ## Paso de punteros como parámetros En C por defecto las variables se pasan **por valor**, no existe el paso por referencia, para emularlo se emplean **direcciones de memoria** (**punteros**). He aquí algunos **ejemplos** de los diferentes pasos de parámetros. ### Paso de punteros por valor ```C // Esta función devuelve 0 o 1 dependiendo de si el puntero contiene NULL int IsNull (tPInteger p) { if (p == NULL) return 1; else return 0; } // Otra opción más correcta sería int IsNull (tPInteger p) { return (p == NULL); } ``` ### Paso de punteros por referencia ```C // Función Swap que intercambia las direcciones de memoria de dos punteros void Swap (tPInteger* p, tPInteger* q) { tPInteger t; if (!IsNull(*p) && !IsNull(*q)) { t = *p; *p = *q; *q = t; } } ``` ## Paso de variables dinámicas como parámetros ### Paso de variables por valor ```C // Función printscr que imprime el contenido de una variable dinámica entera int *p; int printscr1 (int m) { printf("%d \n",m); } int main () { ... printsrc1(*p); ... } // Otra opción sería int *p; int printscr2 (int* m) { printf(" %d \n",*m); } void main () { ... printscr2(p); ... } ``` ### Paso de variables por referencia ```C // Función readInteger para rellenar una variable dinámica int *p; int a; void main () { ... CreateVariable(&p); readInteger(p); readInteger(&a); ... } void readInteger (int* m) { printf(”Give me an integer value: \n”); scanf(” %d”, m); } ``` ## Errores más comunes en el manejo de punteros ### Avisos de compilación más frecuentes (Warnings) - Las variables puntero solo pueden apuntar a datos de **un tipo particular**. Por lo tanto, para que los punteros puedan compararse o asignarse entre sı́ **tienen que ser del mismo tipo**. - Confundir el puntero (p) con la variable a la que apunta (*p). ### Errores de ejecución más frecuentes (Errors) - Las variables referenciadas por puntero (variables dinámicas) solo existen cuado se inicia el apuntador mediante la asignación a **una variable que ya existe** o mediante ```malloc()```. Un error muy frecuente es intentar acceder a la variable cuando no existe. ```C // Incorrecto typedef int* tPos; tPos p; ... *p = ... // Correcto typedef int* tPos; tPos p, q; ... p = malloc(sizeof(int)); *p = ... // O bien q = malloc(sizeof(int)); p = q; *p = ... ```

    - Cuando tenemos varios punteros que apuntan a la misma variable ```*q = 3; p = q; j = q; *j = 5;``` modificar cualquiera de ellos ```q, p o j``` **sobrescribirá el contenido de todas**. Si cualquiera de ellos libera su memoria ```free(p)```, todos quedarán des referenciados. - La memoria de un ordenador **es grande pero no ilimitada**. Un programa **eficiente** es aquel que usa el mínimo de memoria necesaria. Por eso debemos emplear ```free()``` en aquellos punteros que vayamos a modificar. Consejos: 1. Dejar variables a las que no apunta ningún puntero, sin haber hecho un ```free()```. 2. Hacer ```free()``` y no preocuparnos de que apunte a ```NULL```. 3. Hacer un ```free()``` y tratar de acceder posteriormente al puntero.

    Last modified: 02 enero 2025