[ Foro de Pascal ]

Dos dudas sobre procedimientos

20-Jun-2014 23:16
Invitado (FulanitodeTal)
5 Respuestas

Hola soy el FulanitodeTal de hace unos meses, pero es que no me acuerdo de la contraseña y tuve que entrar como visitante. Disculpas.

Tengo dos dudas sobre procedimientos:

Las variables locales se destruyen una vez que se ha terminado el procedimiento. Pero si un procedimiento es recursivo, se llama al mismo antes de salir, por tanto las variables locales todavía están en la memoria. Al iniciar de nuevo el procedimiento se vuelven a declarar. Supongo que se borrarán los valores anteriores y se ponen a cero. ¿O se crean nuevas variables locales y las anteriores permanecen en memoria?

Y la otra duda es un poco más complicada de explicar. Tengo un programa en el cual un procedimiento llama a otro. En teoría, al terminar el segundo se tiene que volver al punto del primero desde donde se llamó al segundo. Pero el segundo procedimiento llama a un tercero antes de terminar. Y el tercero antes de terminar llama al primero. Yo supongo que el tercero al volver al primero no anula todas las vueltas pendientes de hacer (del tercero al segundo y del segundo al primero), porque pasamos al primero sin haber terminado el tercero. Bueno no sé si se entiende lo que quiero decir... Que tengo muchos procedimientos y unos están llamando a otros antes de que se terminen, por lo tanto el programa mantiene muchos retornos pendientes de hacer. Además algunos son recursivos, y según como se desarrolle la ejecución se vuelve a ejecutar el mismo procedimiento o se llama a otro. Esto no me afecta al funcionamiento, es decir el programa hace lo que se espera de él, pero supongo que estará gastando memoria cada vez que se salta de un procedimiento a otro porque tendrá que almacenar el punto de retorno en alguna parte.

Gracias.


21-Jun-2014 00:23
Luis Torres (+12)

Ojalá puedas colocar el código para ver lo que sucede.
Saludos.


22-Jun-2014 15:07
Nacho Cabanes (+31)

Efectivamente, el foro no permite en estos momentos recuperar ni cambiar contraseñas, pero por problemas familiares no estoy pudiendo dedicar tiempo a mejorar la infraestructura.

En el primer caso, se vuelven a crear nuevas copias de las variables, y las anteriores se guardan en "la pila". Eso hace que la recursividad sin fin (o excesiva) pueda llegar a desbordar la pila e interrumpir el flujo del programa (el conocido "Stack Overflow").

En el segundo caso, suena a muy peligroso ("recursividad indirecta"), pero no puedo decirte más sin ver el fuente. En principio, el sistema se encarga de memorizar los valores de las variables locales y los puntos de retorno, así que el programa debería funcionar correctamente si la lógica es buena... pero es un diseño peligroso, que suena a que pueda provocar errores.


22-Jun-2014 16:49
Invitado (FulanitodeTal)

Gracias a los dos por responder.

El programa tiene sobre 1000 lineas y aun está incompleto. Funciona bien, pero no sé si acabará desbordando la memoria si lo uso "intensivamente".

Lo de la recursividad no sé qué voy a hacer, a ver si se me ocurre algo.

Voy a intentar resolver el problema de procedimientos que llaman a otros y luego no retornan. En algunos casos la solución es fácil, por ejemplo en este procedimiento que pego a continuación, se puede eliminar de "case" la opción de volver al menú principal, así se termina el procedimiento y se retorna, porque tal como está escrito se llama al menú principal, y por tanto se deja un retorno pendiente. En otros casos no es tan fácil de resolver porque no se llega al procedimiento desde el menú principal, sino desde otro, y luego no se vuelve a él.

Este que dejo de ejemplo sirve para añadir una ficha a una tabla que ya tiene datos antes de entrar.

Existen variables globales que se usan en el procedimiento:
tabla: array of array of string. Es la matriz de datos, y que ya contiene datos antes de entrar en el procedimiento
num_campos: byte. El número de campos (columnas) que tiene la tabla.
num_registros: word. El número de registros (fichas o filas) que tiene la tabla.
testigo_tabla_desordenada, testigo_tabla_modificada: boolean. Son variables boolean que sirven para saber si el usuario introdujo una nueva ficha. Las fichas se añaden al final, con lo cual el orden alfabético se rompe, y hay que ordenar la tabla antes de guardarla en el disco. Si el usuario intenta salir del programa y los testigos están en "true" se ejecuta un procedimiento que pregunta al usuario si quiere guardar los cambios o no.

Hay declarado previamente un tipo de dato Topcion = set of char;

Se llama a los siguientes procedimientos, que siempre regresan al actual, es decir que no quedan retornos pendientes de hacer:
espacios: elimina los espacios dobles en el texto tecleado, así como los espacios que haya al principio o al final
mayusculas: pasa el texto tecleado a mayúsculas.

Cuando dimensiono una matriz dinámica, siempre lo hago con un dato de más, por ejemplo si quiero que la matriz almacene 5 datos la dimension a 6, porque al dimensionar con Setlength, el compilador numera empezando con el número 0. Si hubiera dimensionado con 5, el compilador numeraría los datos de 0 a 4, y así me
armo un lio muchas veces. Lo que hago es dimensionar a 6, y así el compilador numera los datos de 0 a 5, y en el número 0 no guardo nada, queda sin utilizar.
Excepto en el caso de la matriz "tabla" donde en la fila número 0 guardo los nombres de los campos (ya están introducidos en la tabla al llegar al procedimiento).

El usuario de este programa soy yo, es para mi uso personal.

 
procedure nueva_ficha_procedim;
	var
		bucle: byte;
		ficha_nueva: array of string;
		opciones: Topcion;
		opcion: char;
 
	begin
	clrscr;
 
	Setlength (ficha_nueva, num_campos + 1);
 
	writeln ('  AÑADIR NUEVA FICHA');
	writeln ('  ------------------');
	writeln;
 
	//Guardamos los datos nuevos en ficha_nueva
	for bucle := 1 to num_campos do
		begin
		write ('  ', tabla [0, bucle], ': '); //escribe en pantalla el nombre del campo (los nombres de los campos se guardan en el fila 0 de la tabla)
		readln (ficha_nueva[bucle]);
 
		espacios (ficha_nueva [bucle]);
		mayusculas (ficha_nueva [bucle]);
		end;
 
	//se pasan los datos nuevos de ficha_nueva a tabla
	num_registros := num_registros + 1;
 
	Setlength (tabla, num_registros + 1, num_campos);
 
	for bucle := 1 to num_campos do
		tabla [num_registros, bucle] := ficha_nueva [bucle];
 
	writeln;
	writeln ('  LA NUEVA FICHA HA SIDO AÑADIDA');
 
	testigo_tabla_desordenada := true;
	testigo_tabla_modificada := true;
 
	writeln;
	writeln ('  MENU');
	writeln ('  ----');
	writeln ('  E - AÑADIR OTRA FICHA');
	writeln ('  M - VOLVER AL MENU PRINCIPAL');
	writeln;
	write (' OPCION: ');
 
	opciones := ['E', 'M'];
 
	repeat
		opcion := readkey;
		mayusculas (opcion);
	until opcion in opcions;
 
	case opcion of
		'E':
			nueva_ficha_procedim;
		'M':
			menu_principal_procedim;
		end; //case
 
	end;
 



22-Jun-2014 16:59
Nacho Cabanes (+31)

Eso de que "nueva ficha" llame al menú principal suena muy mal...

El menú principal debería llamar a "nueva ficha", y este procedimiento debería volver al menú cuando termine.

En este caso es fácil evitar la recursividad: basta con que "nueva ficha" tenga un "repeat" que permita introducir varios datos, en vez de llamarse a sí mismo.


22-Jun-2014 19:42
Invitado (FulanitodeTal)

Gracias.

El menú principal ya llama a "nueva ficha". Es desde el único sitio que se puede llegar ahí.

Ya está completamente resuelto el problema:

Para la recursividad efectivamente me valí de un repeat y de una variable local que según el valor que tome se repite o no.

Para la recursividad con parámetros pasados por referencia utilicé variables globales en vez de parámetros por referencia.

Y para los procedimientos que llamen a otros también lo resolví con una variable global de tipo char que según el valor que tome se vuelve al procedimiento anterior y se repite éste (con repeat) o se termina éste y se vuelve al menú principal.






(No se puede continuar esta discusión porque tiene más de dos meses de antigüedad. Si tienes dudas parecidas, abre un nuevo hilo.)