GOTO para niños
GOTO es una instrucción propia de los primeros lenguajes de programación, como BASIC. Esta instrucción suele existir en todos los lenguajes aunque con un mnemónico adaptado al propio lenguaje.
El propósito de la instrucción es transferir el control a un punto determinado del código, donde debe continuar la ejecución. El punto al que se salta, viene indicado por una etiqueta. GOTO es una instrucción de salto incondicional.
La instrucción GOTO ha sido menospreciada en los lenguajes de alto nivel, debido a la dificultad que presenta para poder seguir adecuadamente el flujo del programa, tan necesario para verificar y corregir los programas.
En la actualidad, la funcionalidad de la instrucción sigue existiendo en todos los lenguajes de programación, bajo nombres específicos y con un campo de uso delimitado. Por lo general se restringe su uso a una estructura específica. Lo más habitual es encontrarla como una instrucción de salida de una estructura de control (en bucles FOR...NEXT como Exit FOR, en bucles DO...LOOP como Exit DO', etc...). Y el punto al que se salta, no es arbitrario sino que se salta a puntos específicos coherentes con un flujo fácil de seguir, por lo general el salto es a la instrucción siguiente a la del final de la estructura. Es decir GOTO se ha transformado (bajo la apariencia de otra palabra reservada) como un salto incondicional, pero a un punto concreto en relación a la estructura a la que da soporte.
Contenido
Historia
El salto incondicional no es necesario en un lenguaje de programación de alto nivel. Ya en los años 60 se dieron cuenta de ello, desarrollándose técnicas de programación que no la utilizaban (ver programación estructurada).
Los compiladores e intérpretes de lenguajes de programación aún la tienen, como recurso de programación, pero las técnicas de programación, como la programación estructurada, desaconsejan o prohíben su uso.
El salto incondicional es una instrucción de programación por la cual el contador de programa toma un valor nuevo, que el programador indica. Este método se empleó en las primeras técnicas de programación y en lenguaje de máquina. Cuando la ejecución del programa llega a la instrucción GOTO, la siguiente sentencia ejecutada será la que se encuentre en :Etiqueta.
El abuso de esta, aparentemente ágil, sentencia da lugar a lo que se suele denominar como código espagueti, porque ese es el aspecto del seguimiento del programa, un plato de "espagueti"; esta es una descalificación de una aplicación informática, equivalente a "chapuza indescifrable".
Variaciones
Se pueden encontrar variaciones de la instrucción GOTO. En BASIC, la instrucción ON GOTO puede seleccionar de una lista de diferentes puntos de un programa a los que saltar. Podría ser interpretado como un antecesor de la instrucción switch/case. También, en FORTRAN y algunas versiones de BASIC la línea a la que saltar podía ser indicada mediante una expresión aritmética. Esto último era evitado ya que el código se hacía aún más ilegible teniendo en cuenta la necesidad de hacer los cálculos de la expresión de control para saber el destino del flujo del programa.
Interacción de GOTO dentro de otras estructuras de control
La sentencia GOTO, no solo puede aparecer a lo largo del listado de instrucciones de un programa, sino que incluso puede enviar el flujo dentro de una estructura de control o fuera de la misma.
Es patente, la complejidad de entender un código cuyas estructuras de control lo estuvieran también cruzadas por sentencias GOTO. Si bien el uso de GOTO puede hacer posible situaciones de las que un lenguaje carece, el uso indiscriminado de éstas harían a un código muy difícil de entender y mantener.
Esto deriva en circunstancias especiales que se consideran a continuación:
GOTO envía del flujo fuera de una estructura de control
De modo general GOTO tiene otro enemigo, y es que cuando opera dentro de otra estructura de control que guarda punteros en la pila, si hay una sentencia GOTO que sale de la estructura de control, fuerza a que se vacíe la pila con los punteros de retorno que mantenía la estructura. Esto requiere un chequeo constante de la sentencia GOTO en busca de si aparece o no dentro de una estructura de control (Véase la sección donde se compara GOTO con otras estructuras de control, más adelante en este mismo artículo), para determinar si debe o no retirar de la pila un puntero al que aparentemente no se retornará ya. Una razón para salir de una estructura de control usando GOTO, es que dicha estructura no provea otro mecanismo de salida que llegar al final del bloque de sentencias que contiene la estructura, por lo que los diversos lenguajes han provisto una salida alternativa que solucione la operación que realiza GOTO en dichos casos y a su vez no requiere la verificación que se hacía precisa para cada GOTO, típicamente las sentencias de escape alternativas se han dado en llamar:
- Para las estructura de control que no requieren punto de retorno. Exit flujo donde flujo es la estructura de control, tales como DO, FOR, etc...
- Para las estructuras de control que requieren un punto de retorno, RETURN, tales como GOSUB, FUNCTION, etc..
Las sentencias EXIT, todavía están limitadas respecto de GOTO, pues EXIT siempre devuelve a la siguiente instrucción al final de la estructura de control.
Puede darse el caso en algunos lenguajes, que la ejecución de la sentencia GOTO no conlleve aparejado comprobar si salta fuera de una estructura de control y consecuentemente no elimine de la pila el puntero de retorno que se colocó al entrar a la rutina en dicho caso, a efectos del programa el control sigue dentro de dicha estructura y sigue esperando que alcance el final para volver al punto de retorno y vaciar la pila del valor de retorno, como esto ya no va a ocurrir, se está perdiendo espacio de pila con cada situación de este tipo. También podría darse el caso si algún lenguaje olvida aportar solución que al ir entregando desde la pila los sucesivos retornos, se apunte a un lugar diferente de la que el programador esperaba, por la cascada de entradas fue una y la cascada de salidas debiendo ser en orden inversa conserva una salida que nunca se va a producir, falsificando con ello todas las devoluciones anteriores a esa que se ha quedado perdida por la instrucción goto. Un modo en que algunos lenguajes solventan este problema es utilizando una pila exclusivamente local y eliminarla cuando se sale del procedimiento local, en dicho caso la dirección de retorno se guarda en una pila de ámbito mayor. Si hay punteros remanentes en la pila local no devueltos no producirán errores en cascada hacia atrás.
En situaciones así es difícil seguir la pista de saltos esperada respecto de la que el programador pretendiera hacer si no se comporta como él esperaba. También de este modo es posible hacer que el código se comporte de una forma que el lenguaje no tenía previsto en su diseño y aprovechar con ello una posible situación eficiente, para realizar determinadas tareas.
Adicionalmente para solventar los posibles problemas que el inadecuado uso de GOTO pueda originar, los lenguajes de alto nivel y que utilizan programación mediante bloques y módulos bien definidos y aislados, no permiten sobre GOTO más que operar dentro del bloque o módulo donde se aloja. Un caso típico son las modernas funciones FUNCTION, PROPERTY, etc... donde si bien un GOTO puede aparecer, no puede direccionar a un punto dentro de otra FUNCTION. No confundir este aislamiento con encapsulamiento, aunque la idea es la misma.
GOTO envía el flujo dentro de una estructura de control
Sucede de otra forma cuando un salto por GOTO nos introduce dentro de una estructura de control. Si hay instrucciones tras la etiqueta a la que se salta, éstas se ejecutan correctamente, pero al llegar a una instrucción del final, si la pila está vacía (cuando la estructura de control requiere un puntero de retorno), ocurre un error que según el lenguaje varía, y si la pila no está vacía salta a la dirección que señala el último puntero en la pila, puede considerarse un error si no era lo que perseguía el programador, toda vez que dicho puntero no fue almacenado por dicha estructura decontrol en la que se halla sino por la previa.
Esto solo sucede en aquellas estructuras de control que guardan algún puntero de retorno en la pila, por ejemplo nunca ocurrirá un error dentro de una estructura IF...THEN, ni dentro de una estructura SELECT CASE (Switch), ni DO...LOOP ya que dichas estructuras al no tener al final un punto de retorno, no necesitan almacenar en la pila dicho punto de retorno.
Normalmente, es el caso que en los lenguajes, la ejecución de la sentencia GOTO no conlleva aparejado comprobar si salta dentro de una estructura de control, ya que en principio no debe generar errores aunque tenga un puntero de retorno siempre que este puntero esté localizado en la sentencia de salida en vez de en la pila.
Se detallan, comentados 3 casos comunes:
- Caso de estructura de control donde el puntero de retorno se almacena en la sentencia final: este suele ser DO...LOOP, loop es equivalente a un GOTO DO. En una sección del artículo más adelante, se provee una implementación de la estructura de control DO...LOOP usando GOTO, que ayudará a entender este caso. En este caso por tanto saltar dentro de una estructura DO..LOOP no proporcionará un error si se llega a la sentencia final. (ver sección anterior). El puntero de retorno de esta estructura siempre apunta al principio de la estructura, es por esto que no necesita mantenerlo en la pila t resulta suficiente con dejarlo en la sentencia final.
- Otro caso de estructura de control que el puntero de retorno se almacena en la sentencia final, pero que si da errores: un caso de este tipo suele ser FOR...NEXT. La estructura FOR se compone de 3 secciones operativas, en la primera se inicializan los valores del bucle, en la 2ª se produce el incremento de los valores que controlan el bucle y en la 3ª sección está el bloque de sentencias que se ha de repetir. Si una sentencia GOTO lleva el flujo dentro de un bucle FOR, cuando se alcanza la sentencia NEXT esta sabe localizar el retorno hacia la 2ª sección (en este sentido se comporta como DO...LOOP, (NEXT equivale a GOTO 2ª Sección) pero al tratar de incrementar el bucle descubre que las variables que necesita el bucle no han sido inicializadas, por lo que produce un error de forma indirecta. Un bucle FOR utiliza 4 variables internamente para su propio control, una variable contador que es la que se irá incrementando, 3 variables o constantes que asigna el valor inicial, final y cantidad de incremento respectivamente.
- Caso de estructura de control que el puntero de retorno se almacena en la pila: El caso de este tipo suele ser GOSUB. Cada vez que se ejecuta una sentencia GOSUB (FUNCTION, etc...) se guarda en la pila la dirección de origen, y cada vez que se ejecuta la sentencia RETURN, se salta a la última dirección contenida en la pila, es decir no existe un par asociado GOSUB...RETURN. Véase GOSUB para más detalles. Este caso es el más proclive a que ofrezca un comportamiento difícil de seguir.
GOTO envía el flujo desde dentro de una estructura de control a otro punto dentro
En cualquier caso puede haber innumerables sentencias GOTO dentro de una estructura de control, y recorrerla de forma arbitraria, sin que genere ningún error excepto que se alcance (ejecute su turno) una sentencia de retorno que requiera un puntero de retorno en la pila que no exista. como ya se comentó, si existen valores en la pila puede darse errores o no sobre la base de si esa es la idea que perseguía el programador o no, ya que retornará el último puntero que contenga la pila.
Comparación entre GOTO y otras instrucciones de control del flujo
GOTO es incondicional, es decir ordena el salto de ejecución del programa a una dirección concreta. El salto se solicita porque se requiere que la ejecución continúe desde allí. La problemática de esto resulta en que no resulta evidente el orden de ejecución.
Como se podrá apreciar en las sucesivas comparaciones, GOTO puede implementar todas las estructuras de control, su controversia no se deduce de su versatilidad, si no de la enajenación de claridad en que resulta su uso. Las estructuras de control de lenguajes de alto nivel están realizadas usando GOTO (típicamente JMP, y sus variantes en ensamblador).
Comparación con GOSUB
Al igual que GOTO, GOSUB tampoco acepta (en la mayoría de lenguajes que se utiliza) un salto a una dirección que no sea una constante, es decir no acepta valores desde variables sino solo desde constantes, lo que en cierto modo limita la casuística de errores en que pudiera derivar.
Una diferencia sustancial se establece con las instrucciones GOSUB (saltar a subrutina), la diferencia esencial es que gosub, tras saltar a la instrucción indicada, ejecuta un número determinado de instrucciones hasta que encuentra una instrucción RETURN, que le obliga volver a la siguiente instrucción que viene a continuación de la instrucción GOSUB. GOSUB, por tanto puede entenderse como utilizar 2 gotos, el que salta a la instrucción indicada, y el que retorna a la posición última que se guarda en la pila.
Es por esto que una instrucción GOTO, no guarda el origen de procedencia en la pila. GOSUB, debe necesariamente antes de proceder con el salto guardar en la pila la dirección desde la que salta, para que a su regreso el contador de programa incremente en una unidad.
GOSUB, por tanto delimita no solo el inicio de un grupo de instrucciones sino que también queda claro su final. GOSUB requiere una instrucción de retorno, aunque nada impide colocar varias instrucciones de forma condicionada.
En consecuencia a estas consideraciones GOSUB ofrece un paso más allá el control del flujo que GOTO por sí solo no permite, no obstante, GOTO, puede emular a GOSUB, pues, puede allí donde hubiera un RETURN, remplazarse con un GOTO a la siguiente instrucción del GOTO de origen.
Implementación de GOSUB usando GOTO
Como puede deducirse en el ejemplo a continuación, ambos hacen lo mismo, y la diferencia radica en que usando GOTO, el programador debe controlar todos los saltos (véase líneas GOTO x3), para regresar al punto de partida. GOSUB en cambio hace esto de forma automática, transparente para el programador, que se desentiende de que línea o qué dirección ha de tener el retorno, ya que este valor se guarda en la pila en el momento de procesar la sentencia GOSUB.
Nota previas para entender el ejemplo: instr representan cualquier instrucción. # representa un comentario en la línea a partir de ahí. El número que le sigue representa la continuidad en la posición de memoria, que solo sirve al efecto de comprender mejor el ejemplo. Si este número va entre paréntesis indica una posición relativa respecto de este.
ejemplo de rutina usando GOSUB...
- A = 5 # x-1
- instr # x
- instr # x+1
- GOSUB 400 # instr x+2
- instr # x+3
- ...
- ...
- 400: # instr x+?
- instr # (400)+1
- instr # (400)+2
- Si A es= 2 luego
- instr # (400)+4
- RETURN
- En otro caso
- instr # (400)+7
- instr # (400)+8
- Fin casos
- RETURN
ejemplo de emulación de GOSUB usando GOTO...
- instr # x
- instr # x+1
- GOTO 400 # instr x+2
- X3: # instr x+3
- ...
- ...
- 400: # instr x+?
- instr # (400)+1
- instr # (400)+2
- Si A es= 2 luego
- instr # (400)+4
- GOTO x3
- En otro caso
- instr # (400)+7
- instr # (400)+8
- Fin casos
- GOTO x3
Comparación con IF...THEN...ELSE
Como ya se ha indicado, el salto de GOTO es incondicional, al llegar a ese punto se ejecuta y eso es todo, a un posible lector, no le queda claro cual es la razón de salto, solo sabe a ciencia cierta que la ejecución continúa en el punto indicado y nada más, para comprender mejor que sucede necesita estudiar con detenimiento las condiciones previas, lo que supone una ardua tarea, especialmente cuando se busca código optimizado para velocidad ya que entonces el mismo puede resultar más difícil de entender.
La principal diferencia entre GOTO y la sentencia IF...THEN..ELSE es que GOTO puede basar su salto en los condicionantes que se dan previamente, lo que obliga en la toma de decisiones a utilizar varias sentencias GOTO que lo hace aún más complejo de analizar.
La sentencia IF asocia el salto a un cúmulo de sucesos concretos (puede haber una o varias condiones cuya evaluación conjunta se evalúa como verdadero o falso, de modo que es posible y fácil entender que el salto se produce solo si no se cumple la condición ejecutándose, si se cumple no se salta, se continúa con las siguientes instrucciones y al final de las mismas salta hasta el final de todo el bloque que forman la sentencia y las instrucciones asociadas a sendas condiciones.
Para comprender correctamente como se produce esto es necesario distinguir la sentencia de salto condicional IF que es de alto nivel, con las instrucciones ensamblador de salto condicional (saltar si cero, saltar si no cero, etc...). Son estas últimas quienes determinan si el salto se produce dadas las condiciones.
Comparación con DO...LOOP (WHILE)
= Ejemplos
Pascal
En Pascal, donde se deben declarar las etiquetas con la palabra reservada LABEL:
Program UsoGOTO (input, output);
LABEL
Salto;
BEGIN
Writeln('Como estamos antes del GOTO esto saldrá en pantalla');
GOTO Salto;
Writeln('Esto no saldrá en pantalla');
Salto:
Writeln('Se efectuó una ruptura en el flujo debido a la sentencia GOTO')
END.
Pauscal
En Pauscal es posible usar la instrucción IrHacia (GoTo en español) declarando una etiqueta y estableciéndola como parámetro. Var i:Entero Etiqueta: i = i + 1 IrHacia Etiqueta
C#
En Lenguaje de programación C# se declaran las etiquetas con dos puntos al final.
static void main()
{
:etiqueta: //La etiqueta
:goto etiqueta; //Ir a la etiqueta
}
Basic
En BASIC, las etiquetas se sentencian igual que en C, con dos puntos al final.
:PRINT "Prueba de GOTO en QBASIC"
:GOTO prueba
:PRINT "Esto no se ve porque hemos saltado a la etiqueta llamada prueba"
:prueba:
:PRINT "GOTO realizado correctamente!"
En algunas variantes de BASIC, como por ejemplo Just BASIC o Liberty BASIC, se usa [ y ] para remarcar etiquetas:
:PRINT "Prueba de GOTO en QBASIC"
:GOTO [prueba]
:PRINT "Esto no se ve porque hemos saltado a la etiqueta llamada prueba" [prueba]
:PRINT "GOTO realizado correctamente!"
Ensamblador
En ensamblador se emplean instrucciones similares. En el caso de x86 la instrucción es jmp:
# Programa para GNU/Linux.
# Ensamblar con cc jmp.S -o jmp
# Muestra "Comenzando bucle" y seguidamente entra en un bucle
# mostrando "Hola, mundo" a cada iteración
.section .rodata
msg1:
.string "Hola, mundo"
msg2:
.string "Comenzando bucle"
.section .text
.globl main
main:
movl $msg2,(%esp)
call puts
etiqueta:
movl $msg,(%esp)
call puts
jmp etiqueta
end.
Véase también
En inglés: Goto Facts for Kids
- Estructuras de control
- Bucle
- Bucle for
- Bucle while
- Bucle repetir
- Ciclo infinito
- Código spaghetti
- Teorema del programa estructurado
- Programación estructurada
- Lenguaje de programación