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.
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.
A diferencia de la llamada a una función, no se requiere ningún tipo de preparación o estructura de código para utilizar un GOTO. Como resultado, es más sencillo obtener código inconsistente, incompleto o complicado de mantener. Justamente por ello en los años 60 y 70, cuando surgió la programación estructurada, la comunidad informática se expresó a favor de otras instrucciones de control (if o bucles for y do/while) en lugar de GOTO.
Tal creencia está tan arraigada que el GOTO es muy criticado por los dedicados a la enseñanza de la programación, que lo suelen desaconsejar. Por el otro lado, algunos que si bien admiten el uso de un GOTO como una práctica desaconsejablemanejo de excepciones por ejemplo). Además, por lo general se programan macros o equivalentes para evitar la utilización de GOTO.
reconocen que este es la mejor opción para algunas pocas tareas puntuales en algunos lenguajes de programación (Una crítica famosa a la instrucción en cuestión es una carta redactada por Edsger Dijkstra llamada "Go To Statement Considered Harmful" ('Instrucción Go To Considerada Dañina'). En ella Dijkstra argumenta que el uso irrestricto de GOTO debería ser prohibido en lenguajes de alto nivel ya que dificultan el análisis y la verificación de la corrección de los programas (especialmente aquellos que contienen ciclos). Por el otro lado, Donald Knuth en su libro "Structured Programming with goto Statements" (Programación estructurada con instrucciones Goto), tiene en cuenta ciertas situaciones en las que se utilizaría GOTO. Por lo general, se trata de situaciones en las que una estructura de programación en particular no está disponible y GOTO puede simularla eficientemente.
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:
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:
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.
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:
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.
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).
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.
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.
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.
En Pascal, donde se deben declarar las etiquetas con la palabra reservada LABEL:
En Pauscal es posible usar la instrucción IrHacia (GoTo en español) declarando una etiqueta y estableciéndola como parámetro.
En Lenguaje de programación C# se declaran las etiquetas con dos puntos al final.
En BASIC, las etiquetas se sentencian igual que en C, con dos puntos al final.
En algunas variantes de BASIC, como por ejemplo Just BASIC o Liberty BASIC, se usa [ y ] para remarcar etiquetas:
En ensamblador se emplean instrucciones similares. En el caso de x86 la instrucción es jmp:
end.
Escribe un comentario o lo que quieras sobre GOTO (directo, no tienes que registrarte)
Comentarios
(de más nuevos a más antiguos)