Errores comunes con punteros y listas
Errores comunes con punteros y listas
Errores comunes al utilizar punteros
- 1. Problemas genéricos al utilizar punteros
- 2. Problemas al utilizar listas
- 2.1 No tener en cuenta el caso de la lista vacía o desreferenciar NIL
- 2.2 No asignar NIL al final de la lista
- 2.3 Enganchar mal la lista
- 2.4 Perder el inicio de la lista
- 2.5 No liberar la memoria de una celda a eliminar
- 2.6 Creer que un solo new es suficiente para todos los elementos de la lista
- 2.7 No compartir memoria cuando se pide explícitamente que lo haga (o viceversa)
- 2.8 Desreferenciar incorrectamente un puntero
- 2.9 Generar una celda con basura al final de la lista
1. Problemas genéricos al utilizar punteros
1.1 No reservar memoria para un nuevo nodo
var nuevo : ^Integer; begin { new(nuevo); } nuevo^ := 1; end.
- ERROR: La variable nuevo no tenía memoria reservada. Antes de desreferenciar un puntero, el mismo debe estar apuntando a un lugar de memoria que haya sido reservado previamente utilizando el procedimiento new.
1.2 Asignar NIL luego de reservar memoria
var nuevo : ^Integer; begin new(nuevo); nuevo := NIL; { ! } nuevo^ := 1; end.
- ERROR: El asignar NIL luego de invocar a new no solo hace que el llamado a new pierda sentido sino que también deja un lugar de memoria desperdiciado que es imposible de acceder para liberarlo.
1.3 Inicializar punteros utilizando new para luego asignarlo
var it, nuevo : ^Integer; begin new(it); it^=1; new(nuevo); { ! } nuevo := it; end;
- ERROR: Este es un caso más general del invocar a new y luego asignar NIL. Solo es necesario invocar a new si se necesita generar un nuevo nodo. En otro caso no se debe invocar a new porque sino se reservará un espacio de memoria que luego será inaccesible.
1.4 Creer que una asignación ya genera un alias
var l1, l2 : ^Integer; begin l1 := NIL; l2 := l1; { ! } new(l1); l1^ := 1; if (l1 = l2) then writeLn('l1 y l2 son iguales') else writeLn('l1 y l2 NO son iguales') end;
- El programa imprime el mensaje de la sentencia 'else' por lo que l1 y l2 son distintos. Quien hubiera esperado que sean iguales debe tener en cuenta que el procedimiento new modifica el puntero parámetro, por lo que 'l1' pasa a apuntar al lugar de memoria que se reserva mientras que 'l2' sigue valiendo NIL, que era el valor que se le había asignado.
1.5 Intentar liberar la memoria dos veces en presencia de un alias.
var l1, l2 : ^Integer; begin new(l1); l1^ := 1; l2 := l1; ... dispose(l1); dispose(l2) { ! } end;
- Este problema surge cuando se intenta liberar un espacio de memoria que ya fue liberado. El programa da un error en tiempo de ejecución. Es muy común caer en estas situaciones cuando se manejan alias de punteros.
2. Problemas al utilizar listas
En estos casos se considerará la siguente declaración de tipos:
type ListaInt = ^Nodo; Nodo = record dato : Integer; sig : ListaInt; end;
2.1 No tener en cuenta el caso de la lista vacía o desreferenciar NIL
Procedure insertarFinal(dato : Integer; var lista : ListaInt); var it, nuevo : ListaInt; begin new(nuevo); nuevo^.dato := dato; nuevo^.sig := NIL; { Falta un if que verifique que lista no sea NIL } it := lista; while (it^.sig <> NIL) do it := it^.sig; it^.sig := nuevo end;
- ERROR: No considera que 'lista' puede ser NIL. Normalmente no considerar este caso genera que se intente desreferenciar NIL. En este ejemplo, si la lista es NIL entonces en la primera comprobación del while se desreferencia NIL, lo que genera que el programa aborte.
2.2 No asignar NIL al final de la lista
Procedure insertarFinal(dato : Integer; var lista : ListaInt); var it, nuevo : ListaInt; begin new(nuevo); nuevo^.dato := dato; if (lista = NIL) then lista := nuevo else begin it := lista; while (it^.sig <> NIL) do it := it^.sig; it^.sig := nuevo end; { nuevo^.sig := NIL } end;
- ERROR: Nunca se le asigna NIL al siguiente de 'nuevo' por lo ya no es posible saber cuando termina la lista.
2.3 Enganchar mal la lista
Procedure insertarFinal(dato : Integer; var lista : ListaInt); var it, nuevo : ListaInt; begin new(nuevo); nuevo^.dato := dato; nuevo^.sig := NIL; if (lista = NIL) then lista := nuevo else begin it := lista; while (it <> NIL) do { ! } it := it^.sig; it := nuevo { ! } end end;
- ERROR: En este caso se finaliza la iteración cuando el iterador es NIL y luego al iterador se lo deja apuntando al nuevo nodo. Esto solo modifica el iterador y no la lista.
2.4 Perder el inicio de la lista
Procedure insertarFinal(dato : Integer; var lista : ListaInt); var nuevo : ListaInt; begin new(nuevo); nuevo^.dato := dato; nuevo^.sig := NIL; if (lista = NIL) then lista := nuevo else begin while (lista^.sig <> NIL) do lista := lista^.sig; { ! } lista^.sig := nuevo end end;
- ERROR: Al recorrer la lista utilizando la variable 'lista' se pierde el primer elemento. Esto dejaría varios nodos de la lista inaccesibles si no hay otro puntero que apunte al inicio de la lista.
- PREGUNTAS: Si la variable 'lista' fuese pasada por valor en lugar de por referencia: ¿Se modifica o no la lista? ¿Que pasa cuando la lista es vacía?
2.5 No liberar la memoria de una celda a eliminar
procedure eliminarPrimero(var lista : ListaInt); { var aBorrar : ListaInt; } begin { aBorrar := lista; } lista := lista^.sig; { dispose(aBorrar) } end;
- ERROR: Este procedimiento simplemente asigna como inicio de la lista al segundo elemento, pero no libera la memoria reservada para el primer nodo.
- PREGUNTAS: ¿Qué otro(s) error(es) tiene este procedimiento?
- A TENER EN CUENTA: El liberar la memoria de un nodo es algo que debe hacerse con cuidado porque se debe tener seguridad de que no existen otros punteros que referencien al nodo destruido. Normalmente los procedimientos que liberan memoria indican explícitamente este comportamiento para que pueda ser tenido en cuenta.
2.6 Creer que un solo new es suficiente para todos los elementos de la lista
function copiarLista(lista : ListaInt) : ListaInt; var listaCopia, nodoCopia, itLista : ListaInt; begin if (lista = NIL) then listaCopia := NIL else begin new(listaCopia); listaCopia^.dato := lista^.dato; nodoCopia := listaCopia; itLista := lista^.sig; while (itLista <> NIL) do begin { New(nodoCopia^.sig); } nodoCopia := nodoCopia^.sig; nodoCopia^.dato := itLista^.dato; itLista := itLista^.sig end; nodoCopia^.sig := NIL; copiarLista := listaCopia end end;
- ERROR: El error consiste en solo reservar memoria para el primer elemento cuando se debe reservar memoria para cada elemento de la lista a copiar.
- A TENER EN CUENTA: Otros errores asociados a este tipo de ejercicios donde se debe recorrer más de una lista, es el orden de las sentencias que avanzan en las listas. Observar que si se altera el orden de las sentencias del while el programa no se comporta como se quiere.
2.7 No compartir memoria cuando se pide explícitamente que lo haga (o viceversa)
Suponiendo que la función debe devolver una copia limpia de la lista parámetro a partir del segundo elemento, o NIL si la lista es vacía:
function copiaResto(lista : ListaInt) : ListaInt; begin if (lista = NIL) then copiaResto := NIL else copiaResto := lista^.sig; { ! } { copiaResto := copiarLista(lista^.sig) } { Esta sería la sentencia correcta } end;
- ERROR: Esta implementación simplemente devuelve el resto de la lista pasada por parámetro, por lo que comparte memoria.
2.8 Desreferenciar incorrectamente un puntero
var lista : ListaInt; begin new(lista); lista.dato := 5; { ERROR: falta el operador ^ para desreferenciar el puntero } lista^sig := NIL; { ERROR: falta el operador . para acceder al campo del registro } lista.Nodo.dig := 5; { ERROR: no se debe pasar por el tipo del nodo } lista^ := lista^.sig; { ERROR: asigna un puntero a un registro } lista := ^lista.sig; { ERROR: el operador ^ no está en el lugar correcto } lista := lista.sig^ { ERROR: el operador ^ no está en el lugar correcto } end.
2.9 Generar una celda con basura al final de la lista
Suponiendo que se está realizando una recorrida dentro de la cual se van generando celdas para una nueva lista, un error común es ir generando anticipadamente la próxima celda. Esto ocasiona que, tras haber generado la última celda de la nueva lista, quede al final una celda adicional con basura, que nunca se llega a cargar con un valor válido.
var lista, aux : ListaInt; begin { uso lista para ir recorriendo y aux para ir generando la nueva lista }
while (lista <> NIL) do begin ... aux^.dato := p^.dato; { copio el dato actual en la celda creada en la entrada previa al while } new (aux.sig); { ERROR: creo anticipadamente la siguiente celda } end;
{ cuando salí del while, quedó una celda con basura al final de la lista }
end.
Última modificación: martes, 20 de noviembre de 2018, 19:05