Subrotina
a ideia de uma subrotina foi trabalhada depois que máquinas de computação já existiam por algum tempo.As instruções de salto aritmético e condicional foram planejadas antes do tempo e mudaram relativamente pouco, mas as instruções especiais usadas para chamadas de procedimento mudaram muito ao longo dos anos.The earliest computers and microprocessors, such as the Manchester Baby and the RCA 1802, did not have a single subroutine call instruction.Sub-rotinas poderiam ser implementadas, mas eles exigiram programadores para usar a sequência de chamada—uma série de instruções—em cada local de chamada.sub-rotinas foram implementadas no Z4 de Konrad Zuse em 1945.
em 1945, Alan M. Turing usou os Termos “bury” e “unbury” como um meio de chamar e retornar das sub-rotinas.em janeiro de 1947, John Mauchly apresentou Notas gerais em “a Symposium of Large Scale Digital Calculating Machinery”, sob o patrocínio conjunto da Universidade de Harvard e do Bureau of Ordnance, Marinha dos Estados Unidos. Aqui ele discute a operação serial e paralela sugerindo
…a estrutura da máquina não precisa ser complicada nem um pouco. É possível, uma vez que todas as características lógicas essenciais a este procedimento estão disponíveis, evoluir uma instrução de codificação para colocar as sub-rotinas na memória em lugares conhecidos da máquina, e de tal forma que elas possam ser facilmente chamadas em uso.
em outras palavras, pode-se designar subrotina a como divisão e subrotina B como multiplicação complexa e subrotina C como a avaliação de um erro padrão de uma sequência de Números, e assim por diante através da lista de subrotinas necessárias para um problema particular. … Todas estas sub-rotinas serão então armazenadas na máquina, e tudo o que se precisa fazer é fazer uma breve referência a elas por número, como elas são indicadas na codificação.
Kay McNulty tinha trabalhado de perto com John Mauchly na equipe ENIAC e desenvolveu uma ideia para sub-rotinas para o computador ENIAC que ela estava programando durante a Segunda Guerra Mundial. Goldstine e von Neumann escreveram um artigo de 16 de agosto de 1948 discutindo o uso de sub-rotinas.
Alguns muito cedo computadores e microprocessadores, tal como o IBM 1620, o Intel 4004 e Intel 8008, e os microcontroladores PIC, tem uma única instrução de chamada de sub-rotina que usa um hardware dedicado pilha para armazenar endereços de retorno—como hardware suporta apenas alguns níveis de sub-rotina de nidificação, mas pode suportar sub-rotinas recursivas. Máquinas antes de meados da década de 1960-como o UNIVAC I, o PDP—1, e o IBM 1130-normalmente usam uma convenção de chamada que salvou o contador de instruções na primeira localização de memória da chamada subrotina. Isto permite níveis arbitrariamente profundos de nidificação sub-rotina, mas não suporta sub-rotinas recursivas. O PDP-11 (1970) é um dos primeiros computadores com uma instrução de chamada sub-rotina de stack-pushing; esta funcionalidade suporta ambos os ninhos sub-rotineiros arbitrariamente profundos e também suporta sub-rotinas recursivas.
suporte à linguagem
nos primeiros montadores, o suporte a subrotinas era limitado. Sub-rotinas não foram explicitamente separadas umas das outras ou do programa principal, e de fato o código fonte de uma sub-rotina poderia ser intercalado com o de outros subprogramas. Alguns montadores ofereciam macros pré-definidos para gerar as sequências de chamadas e retornos. Na década de 1960, os Montadores geralmente tinham um suporte muito mais sofisticado tanto para as sub-rotinas internas como montadas separadamente que poderiam ser ligadas entre si.
subrotina librariesEdit
mesmo com esta abordagem complicada, as sub rotinas revelaram-se muito úteis. Por um lado, eles permitiram o uso do mesmo código em muitos programas diferentes. Além disso, a memória era um recurso muito escasso nos primeiros computadores, e as sub-rotinas permitiram uma economia significativa no tamanho dos programas.muitos dos primeiros computadores carregaram as instruções do programa na memória a partir de uma fita de papel perfurada. Cada subrotina poderia então ser fornecida por um pedaço separado de fita, carregada ou spliced antes ou depois do programa principal (ou “mainline”); e a mesma fita subrotina poderia então ser usada por muitos programas diferentes. A similar approach applied in computers that used punched cards for their main input. O nome biblioteca subrotina originalmente significava uma biblioteca, no sentido literal, que mantinha coleções indexadas de fitas ou baralhos de cartas para uso coletivo.
Retorno indireto jumpEdit
Para remover a necessidade de auto-modificação de código, designers de computador eventualmente fornecido indireta instrução de salto, cuja operando, em vez de ser o endereço de retorno em si, foi a localização de uma variável ou processador registrador que contém o endereço de retorno.
nesses computadores, em vez de modificar o salto de retorno da sub-rotina, o programa de chamada armazenaria o endereço de retorno em uma variável de modo que, quando a sub-rotina terminasse, executaria um salto indireto que direcionaria a execução para o local dado pela variável predefinida.
Saltar para subroutineEdit
outro avanço foi o salto para instrução subrotina, que combinou a gravação do endereço de retorno com o salto de chamada, minimizando assim a sobrecarga significativamente.
no sistema IBM/360, por exemplo, as instruções do ramo BAL ou BALR, projetadas para a chamada de procedimento, guardariam o endereço de retorno em um registro de processador especificado na instrução, pelo registro Convenção 14. Para retornar, a subrotina teve apenas que executar uma instrução indireta de ramo (BR) através desse registro. Se a subrotina necessitasse desse registro para algum outro propósito (como chamar outra subrotina), salvaria o conteúdo do registro para um local de memória privada ou uma pilha de registro.
em sistemas como o HP 2100, a instrução JSB executaria uma tarefa semelhante, exceto que o endereço de retorno foi armazenado no local de memória que era o alvo do ramo. A execução do procedimento realmente começaria no próximo local de memória. Na linguagem HP 2100 assembly, seria escrito, por exemplo
... JSB MYSUB (Calls subroutine MYSUB.) BB ... (Will return here after MYSUB is done.)
para chamar uma subrotina chamada MYSUB do programa principal. A subrotina seria codificada como
MYSUB NOP (Storage for MYSUB's return address.) AA ... (Start of MYSUB's body.) ... JMP MYSUB,I (Returns to the calling program.)
a instrução JSB colocou o endereço da instrução seguinte (ou seja, BB) no local especificado como seu operando (ou seja, MYSUB), e então ramificada para o próximo local depois disso (ou seja, AA = MYSUB + 1). A subrotina poderia então retornar ao programa principal executando o salto indireto JMP MYSUB, I que ramificou para o local armazenado no local MYSUB.compiladores para Fortran e outras línguas poderiam facilmente fazer uso destas instruções quando disponíveis. Esta abordagem suportou vários níveis de chamadas; no entanto, uma vez que o endereço de retorno, parâmetros e valores de retorno de uma subrotina foram atribuídos locais de memória fixa, ele não permitiu chamadas recursivas.
incidentalmente, um método similar foi usado pelo Lotus 1-2-3, no início da década de 1980, para descobrir as dependências de recálculo em uma planilha. Ou seja, um local foi reservado em cada célula para armazenar o endereço de retorno. Uma vez que as referências circulares não são permitidas para a ordem natural de recálculo, isso permite uma caminhada em árvore sem reservar espaço para uma pilha de memória, que era muito limitada em pequenos computadores como o IBM PC.
Call stackEdit
a maioria das implementações modernas de uma chamada sub-rotina usam uma pilha de chamadas, um caso especial da estrutura de dados da pilha, para implementar chamadas sub-rotinas e retornos. Cada chamada de procedimento cria um novo item, chamado de frame de pilha, no topo da pilha; quando o procedimento retorna, seu quadro de pilha é excluído da pilha, e seu espaço pode ser usado para outras chamadas de procedimento. Cada pilha contém os dados privados da chamada correspondente, que normalmente inclui os parâmetros do procedimento e variáveis internas, e o endereço de retorno.
a sequência de chamadas pode ser implementada por uma sequência de instruções ordinárias (uma abordagem ainda usada em computação de conjunto de instruções reduzidas (RISC) e arquitetura de palavra de instrução muito longa (VLIW)), mas muitas máquinas tradicionais projetadas desde o final da década de 1960 têm incluído instruções especiais para esse propósito.
a pilha de chamadas é normalmente implementada como uma área contígua da memória. É uma escolha de design arbitrária se o fundo da pilha é o endereço mais baixo ou mais alto dentro desta área, de modo que a pilha pode crescer para a frente ou para trás na memória; no entanto, muitas arquiteturas escolheram esta última.
alguns projetos, notavelmente algumas implementações Forth, usaram duas pilhas separadas, uma principalmente para informações de controle (como endereços de retorno e contadores de loop) e a outra para dados. O primeiro era, ou funcionava como, uma pilha de chamadas e era apenas indiretamente acessível ao programador através de outras construções de linguagem, enquanto o último era mais diretamente acessível.
Quando chamadas de procedimentos baseados em pilha foram introduzidas pela primeira vez, uma motivação importante foi salvar memória preciosa. Com este esquema, o compilador não tem que reservar espaço separado na memória para os dados privados (parâmetros, endereço de retorno e variáveis locais) de cada procedimento. A qualquer momento, a pilha contém apenas os dados privados das chamadas que estão atualmente ativas (ou seja, que foram chamadas, mas ainda não retornaram). Por causa das maneiras em que os programas eram geralmente montados a partir de bibliotecas, não era (e ainda é) incomum encontrar programas que incluem milhares de sub-rotinas, das quais apenas um punhado estão ativos em qualquer momento. Para tais programas, o mecanismo de pilha de chamadas poderia salvar quantidades significativas de memória. Na verdade, o mecanismo de pilha de chamadas pode ser visto como o método mais antigo e mais simples para a gestão automática da memória.
no entanto, outra vantagem do método da pilha de chamadas é que ele permite chamadas sub-rotinas recursivas, uma vez que cada chamada aninhada para o mesmo procedimento recebe uma instância separada de seus dados privados.
empilhamento tardio Edit
uma desvantagem do mecanismo de pilha de chamadas é o aumento do custo de uma chamada de procedimento e seu retorno correspondente. O custo extra inclui aumentar e diminuir o ponteiro da pilha (e, em algumas arquiteturas, verificar o overflow da pilha), e acessar as variáveis e parâmetros locais por endereços de frame-relative, em vez de endereços absolutos. O custo pode ser realizado em maior tempo de execução, ou maior complexidade do processador, ou ambos.
This overhead is most obvious and objectionable in leaf procedures or leaf functions, which return without making any procedure calles themselves.Para reduzir essa sobrecarga, muitos compiladores modernos tentam atrasar o uso de uma pilha de chamadas até que ela seja realmente necessária. Por exemplo, a chamada de um procedimento P pode armazenar o endereço de retorno e os parâmetros do procedimento chamado em certos registros do processador, e o controle de transferência para o corpo do procedimento por um simples salto. Se o procedimento P retornar sem fazer qualquer outra chamada, a pilha de chamadas não é usada em tudo. Se P precisa chamar outro procedimento Q, ele irá então usar a pilha de chamadas para salvar o conteúdo de quaisquer registros (como o endereço de retorno) que serão necessários após Q retorna.