Subrutina
La idea de una subrutina se desarrolló después de que las máquinas de computación ya existieran durante algún tiempo.Las instrucciones de salto aritmético y condicional se planificaron con anticipación y han cambiado relativamente poco, pero las instrucciones especiales utilizadas para las llamadas de procedimiento han cambiado mucho a lo largo de los años.Los primeros ordenadores y microprocesadores, como el Manchester Baby y el RCA 1802, no tenían una sola instrucción de llamada de subrutina.Se podían implementar subrutinas, pero requerían que los programadores usaran la secuencia de llamadas – una serie de instrucciones-en cada sitio de llamadas.
Las subrutinas se implementaron en el Z4 de Konrad Zuse en 1945.
En 1945, Alan M. Turing usó los términos «enterrar » y» desenterrar » como un medio para llamar y regresar de subrutinas.
En enero de 1947, John Mauchly presentó notas generales en ‘A Symposium of Large Scale Digital Calculating Machinery’, patrocinado conjuntamente por la Universidad de Harvard y el Bureau of Ordnance de la Armada de los Estados Unidos. Aquí expone en serie y en paralelo sugiriendo
…la estructura de la máquina no necesita ser complicado un poco. Es posible, ya que todas las características lógicas esenciales para este procedimiento están disponibles, desarrollar una instrucción de codificación para colocar las subrutinas en la memoria en lugares conocidos por la máquina, y de tal manera que puedan ser fácilmente llamadas a uso.
En otras palabras, se puede designar la subrutina A como división y la subrutina B como multiplicación compleja y la subrutina C como evaluación de un error estándar de una secuencia de números, y así sucesivamente a través de la lista de subrutinas necesarias para un problema particular. … Todas estas subrutinas se almacenarán en la máquina, y todo lo que hay que hacer es hacer una breve referencia a ellas por número, como se indica en la codificación.
Kay McNulty había trabajado en estrecha colaboración con John Mauchly en el equipo de ENIAC y desarrolló una idea de subrutinas para la computadora ENIAC que estaba programando durante la Segunda Guerra Mundial. Ella y los otros programadores de ENIAC usaron las subrutinas para ayudar a calcular las trayectorias de los misiles.
Goldstine y von Neumann escribieron un artículo fechado el 16 de agosto de 1948 en el que discutían el uso de subrutinas.
Algunos ordenadores y microprocesadores muy tempranos, como el IBM 1620, el Intel 4004 e Intel 8008, y los microcontroladores PIC, tienen una llamada de subrutina de una sola instrucción que utiliza una pila de hardware dedicada para almacenar direcciones de retorno—este hardware soporta solo unos pocos niveles de anidamiento de subrutinas, pero puede soportar subrutinas recursivas. Las máquinas anteriores a mediados de la década de 1960, como el UNIVAC I, el PDP-1 y el IBM 1130, generalmente usan una convención de llamada que guarda el contador de instrucciones en la primera ubicación de memoria de la subrutina llamada. Esto permite niveles arbitrariamente profundos de anidamiento de subrutinas, pero no admite subrutinas recursivas. El PDP-11 (1970) es uno de los primeros ordenadores con una instrucción de llamada a subrutinas que empujan la pila; esta característica soporta tanto anidamiento arbitrariamente profundo de subrutinas como también soporta subrutinas recursivas.
Soporte de idiomadItar
En los primeros ensambladores, el soporte de subrutinas era limitado. Las subrutinas no estaban explícitamente separadas entre sí o del programa principal, y de hecho el código fuente de una subrutina podía intercalarse con el de otros subprogramas. Algunos ensambladores ofrecerían macros predefinidas para generar las secuencias de llamada y retorno. En la década de 1960, los ensambladores generalmente tenían un soporte mucho más sofisticado para subrutinas en línea y ensambladas por separado que podían vincularse entre sí.
Bibliotecas de subrutinas Edit
Incluso con este engorroso enfoque, las subrutinas demostraron ser muy útiles. Por un lado, permitieron el uso del mismo código en muchos programas diferentes. Además, la memoria era un recurso muy escaso en las primeras computadoras, y las subrutinas permitían ahorros significativos en el tamaño de los programas.
Muchas de las primeras computadoras cargaban las instrucciones del programa en la memoria desde una cinta de papel perforada. Cada subrutina podría ser provista por un pedazo de cinta separado, cargado o empalmado antes o después del programa principal( o «línea principal»); y la misma cinta de subrutina podría ser utilizada por muchos programas diferentes. Un enfoque similar aplicado en computadoras que usaban tarjetas perforadas para su entrada principal. El nombre biblioteca de subrutinas originalmente significaba una biblioteca, en el sentido literal, que mantenía colecciones indexadas de cintas o barajas de cartas para uso colectivo.
Retorno por salto indirectoeditar
Para eliminar la necesidad de código auto-modificable, los diseñadores de computadoras eventualmente proporcionaron una instrucción de salto indirecto, cuyo operando, en lugar de ser la dirección de retorno en sí, era la ubicación de una variable o registro de procesador que contenía la dirección de retorno.
En esos equipos, en lugar de modificar el salto de retorno de la subrutina, el programa que llama almacenaría la dirección de retorno en una variable de modo que cuando la subrutina se completara, ejecutaría un salto indirecto que dirigiría la ejecución a la ubicación dada por la variable predefinida.
Saltar a subrutinaeditar
Otro avance fue la instrucción de saltar a subrutina, que combinaba el ahorro de la dirección de retorno con el salto de llamada, minimizando así la sobrecarga significativamente.
En el IBM System/360, por ejemplo, las instrucciones de sucursal BAL o BALR, diseñadas para llamadas de procedimientos, guardarían la dirección de retorno en un registro de procesador especificado en la instrucción, por registro de convención 14. Para regresar, la subrutina solo tenía que ejecutar una instrucción de rama indirecta (BR) a través de ese registro. Si la subrutina necesitaba ese registro para algún otro propósito (como llamar a otra subrutina), guardaría el contenido del registro en una ubicación de memoria privada o en una pila de registros.
En sistemas como el HP 2100, la instrucción JSB realizaba una tarea similar, excepto que la dirección de retorno se almacenaba en la ubicación de memoria que era el objetivo de la rama. La ejecución del procedimiento comenzaría en la siguiente ubicación de memoria. En el lenguaje ensamblador HP 2100, se escribiría, por ejemplo
... JSB MYSUB (Calls subroutine MYSUB.) BB ... (Will return here after MYSUB is done.)
para llamar a una subrutina llamada MYSUB desde el programa principal. La subrutina se codificaría como
MYSUB NOP (Storage for MYSUB's return address.) AA ... (Start of MYSUB's body.) ... JMP MYSUB,I (Returns to the calling program.)
La instrucción ACC coloca la dirección de la SIGUIENTE instrucción (es decir, BB) en la ubicación especificada como su operando (es decir, MYSUB), y luego se añadió a la SIGUIENTE posición después de eso (es decir, AA = MYSUB + 1). La subrutina podría regresar al programa principal ejecutando el salto indirecto JMP MYSUB, I que se ramificó a la ubicación almacenada en la ubicación MYSUB.
Los compiladores para Fortran y otros lenguajes podrían usar fácilmente estas instrucciones cuando estén disponibles. Este enfoque admitía múltiples niveles de llamadas; sin embargo, dado que a la dirección de retorno, los parámetros y los valores de retorno de una subrutina se les asignaban ubicaciones de memoria fijas, no permitía llamadas recursivas.
Por cierto, Lotus 1-2-3 utilizó un método similar, a principios de la década de 1980, para descubrir las dependencias de recálculo en una hoja de cálculo. A saber, se reservó un lugar en cada celda para almacenar la dirección del remitente. Dado que las referencias circulares no están permitidas para el orden de recálculo natural, esto permite un recorrido por árboles sin reservar espacio para una pila en memoria, lo que era muy limitado en computadoras pequeñas como el IBM PC.
Pila de calleseditar
La mayoría de las implementaciones modernas de una llamada de subrutina utilizan una pila de llamadas, un caso especial de la estructura de datos de la pila, para implementar llamadas y devoluciones de subrutinas. Cada llamada de procedimiento crea una nueva entrada, llamada marco de pila, en la parte superior de la pila; cuando el procedimiento regresa, su marco de pila se elimina de la pila, y su espacio se puede usar para otras llamadas de procedimiento. Cada marco de pila contiene los datos privados de la llamada correspondiente, que normalmente incluye los parámetros y variables internas del procedimiento, y la dirección de retorno.
La secuencia de llamadas se puede implementar mediante una secuencia de instrucciones ordinarias (un enfoque que todavía se usa en arquitecturas de computación de conjuntos de instrucciones reducidos (RISC) y de palabras de instrucción muy largas (VLIW)), pero muchas máquinas tradicionales diseñadas desde finales de la década de 1960 han incluido instrucciones especiales para ese propósito.
La pila de llamadas generalmente se implementa como un área de memoria contigua. Es una elección de diseño arbitraria si la parte inferior de la pila es la dirección más baja o más alta dentro de esta área, de modo que la pila puede crecer hacia adelante o hacia atrás en la memoria; sin embargo, muchas arquitecturas eligieron esta última.
Algunos diseños, en particular algunas implementaciones Forth, usaban dos pilas separadas, una principalmente para información de control (como direcciones de retorno y contadores de bucle) y la otra para datos. El primero era, o funcionaba como, una pila de llamadas y solo era accesible indirectamente para el programador a través de otras construcciones de lenguaje, mientras que el segundo era más accesible directamente.
Cuando se introdujeron por primera vez las llamadas a procedimientos basados en pilas, una motivación importante era guardar una memoria preciosa. Con este esquema, el compilador no tiene que reservar espacio separado en la memoria para los datos privados (parámetros, dirección de retorno y variables locales) de cada procedimiento. En cualquier momento, la pila contiene solo los datos privados de las llamadas que están activas actualmente (es decir, que se han llamado pero aún no han regresado). Debido a las formas en que los programas se ensamblaban a partir de bibliotecas, era (y sigue siendo) común encontrar programas que incluyeran miles de subrutinas, de las cuales solo un puñado están activas en un momento dado. Para tales programas, el mecanismo de pila de llamadas podría ahorrar cantidades significativas de memoria. De hecho, el mecanismo de pila de llamadas puede verse como el método más antiguo y sencillo para la gestión automática de la memoria.
Sin embargo, otra ventaja del método de pila de llamadas es que permite llamadas de subrutinas recursivas, ya que cada llamada anidada al mismo procedimiento obtiene una instancia separada de sus datos privados.
Edición de apilamiento retardado
Una desventaja del mecanismo de pila de llamadas es el aumento del costo de una llamada de procedimiento y su retorno coincidente. El coste adicional incluye el incremento y decremento del puntero de pila (y, en algunas arquitecturas, la comprobación del desbordamiento de pila), y el acceso a las variables y parámetros locales mediante direcciones relativas a fotogramas, en lugar de direcciones absolutas. El costo se puede realizar en un mayor tiempo de ejecución, o una mayor complejidad del procesador, o ambos.
Esta sobrecarga es más obvia y objetable en procedimientos de hojas o funciones de hojas, que regresan sin hacer ningún llamado de procedimiento.Para reducir esa sobrecarga, muchos compiladores modernos intentan retrasar el uso de una pila de llamadas hasta que sea realmente necesaria. Por ejemplo, la llamada de un procedimiento P puede almacenar la dirección de retorno y los parámetros del procedimiento llamado en ciertos registros del procesador, y transferir el control al cuerpo del procedimiento mediante un simple salto. Si el procedimiento P regresa sin hacer ninguna otra llamada, la pila de llamadas no se utiliza en absoluto. Si P necesita llamar a otro procedimiento Q, entonces usará la pila de llamadas para guardar el contenido de cualquier registro (como la dirección de devolución) que se necesitará después de que Q regrese.