From 9e4a29c9cd277a501b83e5dadfe7a8e4ba7822ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Portas=20L=C3=B3pez?= <81629707+TeenBiscuits@users.noreply.github.com> Date: Thu, 29 Feb 2024 13:34:44 +0100 Subject: [PATCH] Mejora del Tema 2 TGR 29/02 --- docs/topics/Bloque-1.md | 12 +- docs/topics/Tema-2.md | 295 ++++++++++++++++++++++++++++++---------- 2 files changed, 230 insertions(+), 77 deletions(-) diff --git a/docs/topics/Bloque-1.md b/docs/topics/Bloque-1.md index 9455f13..f1a67eb 100644 --- a/docs/topics/Bloque-1.md +++ b/docs/topics/Bloque-1.md @@ -744,11 +744,11 @@ Las direcciones se pueden almacenar en variables de puntero especiales. ```mermaid flowchart LR subgraph Puntero - Dirección + Dirección end - + subgraph Variable - vc[Contenido] + vc[Contenido] end Dirección --> Variable @@ -807,11 +807,11 @@ int i, *p = &i; ```mermaid flowchart LR subgraph p - &i + &i end - + subgraph i - vc[?] + vc[?] end &i --> i diff --git a/docs/topics/Tema-2.md b/docs/topics/Tema-2.md index fb4fb24..cea9939 100644 --- a/docs/topics/Tema-2.md +++ b/docs/topics/Tema-2.md @@ -22,15 +22,17 @@ Existen dos lugares en memoria para almacenar elementos: la pila (**stack**) y e | ... | 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 -- 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. +- 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. @@ -57,21 +59,26 @@ 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;``` +- ```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;``` -En el lenguaje C este valor nulo se representa con la constante NULL. +En el lenguaje C este valor nulo se representa con la constante NULL, esta constante es la dirección de memoria nula. ### 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```. +Para crear la variable dinámica apuntada por un puntero se utiliza el operador ```malloc``` definido en el archivo de +cabecera ```stdlib```. ```C #include @@ -79,23 +86,22 @@ P = malloc(sizeof(int)); // Memoria suficiente para un entero ``` - ```mermaid flowchart TB subgraph Memoria - subgraph Stack - subgraph P - Dirección[3F5000AC o NULL] - end - end - - subgraph Heap - subgraph Variable[int = *P] - Contenido[________] - end - end + subgraph Stack + subgraph P + Dirección[3F5000AC o NULL] + end + end + + subgraph Heap + subgraph Variable[int = *P] + Contenido[________] + end + end end - + Dirección --> Variable ``` @@ -107,7 +113,8 @@ En caso de no existir memoria suficiente disponible al puntero P se 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. +Se marcará la variable a la que apunta el puntero como liberada, aunque **la información no es destruida**, ya no es +accesible. ```C #include @@ -117,20 +124,20 @@ free(P); ```mermaid flowchart TB subgraph Memoria - subgraph Stack - subgraph P - Dirección[3F5000AC o NULL] - end - end - - subgraph Heap - subgraph Variable[Memoria liberada] - Contenido[________] - end - end + subgraph Stack + subgraph P + Dirección[3F5000AC o NULL] + end + end + + subgraph Heap + subgraph Variable[Memoria liberada] + Contenido[________] + end + end end - - Dirección --X--> Variable + + Dirección -- X --> Variable ``` #### Acceso a una variable dinámica @@ -168,16 +175,17 @@ P = Q; // Asignamos al puntero el valor de otro puntero 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: +Es decir ahora ```P``` **no** tiene la dirección de memoria que se le asignó al liberar la memoria, **tiene la +de ```Q```**. Visualmente: ```mermaid flowchart LR subgraph Stack - P - Q + P + Q end subgraph Heap - 11 + 11 end Q --> 11 @@ -186,12 +194,12 @@ flowchart LR ```mermaid flowchart LR subgraph Stack - P - Q + P + Q end subgraph Heap - __ - 11 + __ + 11 end P --> __ @@ -201,12 +209,12 @@ flowchart LR ```mermaid flowchart LR subgraph Stack - P - Q + P + Q end subgraph Heap - 3 - 4 + 3 + 4 end P --> 3 @@ -216,12 +224,12 @@ flowchart LR ```mermaid flowchart LR subgraph Stack - P - Q + P + Q end subgraph Heap - 3 - 4 + 3 + 4 end P --> 4 @@ -231,18 +239,25 @@ flowchart LR ```mermaid flowchart LR subgraph Stack - P - Q + P + Q end subgraph Heap - 3 - 11 + 3 + 11 end P --> 11 Q --> 11 ``` + + +En este código hay un error, al cambiar la dirección de P, debíamos hacer ```free(P)```. Al no hacerlo el ```3``` queda +**ocupando memoria** hasta el final de la ejecución. + + + ### Comparación de punteros ```C @@ -258,17 +273,17 @@ title: Identidad --- flowchart LR subgraph P - dirP[3F5000AC] + dirP[3F5000AC] end subgraph Q - dirQ[3F5000AC] + dirQ[3F5000AC] end subgraph valor[*P *Q] 11 end - + dirP & dirQ --> valor - + ``` ```mermaid @@ -277,10 +292,10 @@ title: Igualdad --- flowchart LR subgraph P - dirP[3F5000AC] + dirP[3F5000AC] end subgraph Q - dirQ[600A232B] + dirQ[600A232B] end subgraph valorP[*P] vP[11] @@ -288,12 +303,11 @@ flowchart LR subgraph valorQ[*Q] vQ[11] end - + dirP --> valorP dirQ --> valorQ - -``` +``` ## Resumen sobre el uso de punteros y variables dinámicas @@ -314,7 +328,7 @@ flowchart LR - free(p); + free(p); // Liberar memoria (borrar valor) p = NULL;

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

@@ -338,4 +352,143 @@ flowchart LR p == NULL; p != NULL; // Comparación con valor nulo
-
\ No newline at end of file + + +## 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 = ... + ``` + + + Los punteros acceden directamente a la memoria del ordenador, por tanto, pueden intentar acceder a posiciones no + reservadas si no son inicializados correctamente. Una **solución sencilla** es siempre iniciar vectores **apuntando + a ```NULL```**. + + + +- 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.