7. Funções¶
“Função” é o termo que se usa na linguagem C para designar uma sequência de instruções que realizam uma tarefa específica e pode ser invocada de diferentes contextos. Também se usam na programação em geral termos como “rotina”, “subrotina”, “método” (em linguagens OO) ou “procedimento” (SQL), com aproximadamente o mesmo significado.
O objetivo da sua utilização é subdividir e organizar os programas, eventualmente extensos e complexos, em partes mais pequenas, mais simples e reutilizáveis.
Neste texto usa-se o termo “função”, por coerência com a utilização da linguagem C.
Uma característica importante num processador é o suporte à implementação de funções, ou seja, a existência de um mecanismo que possibilite invocar um mesmo troço de programa a partir de outro ponto do programa e retornar ao ponto de invocação. Esta funcionalidade é concretizada pela ação que transfere a execução para o endereço onde reside a função (de modo semelhante a uma instrução de salto) e simultaneamente memoriza o endereço corrente. A ação de retorno consiste em repor o endereço memorizado no PC.
No P16, durante a execução de uma instrução, o PC contém o endereço da instrução seguinte.
Se este valor for guardado como o endereço de retorno, fica assegurado o retorno à posição correta.
O endereço de retorno é guardado no registo R14, que devido a esta funcionalidade
tem também o nome de registo de ligação (link register ou LR).
A instrução BL, antes de afetar o PC com o endereço da função,
transfere o valor atual do PC para o LR.
O retorno ao ponto de invocação, faz-se copiando o conteúdo de LR para o PC.
Por exemplo, com a instrução mov pc, lr.
7.1. Função sem parâmetros¶
Considere-se a sequência de duas chamadas à função void delay().
...
delay();
...
delay();
...
(a) |
1...
2bl delay
3...
4bl delay
5...
(b) |
A chamada a função sem parâmetros e sem valor de retorno
corresponde apenas à execução da instrução bl.
No programa da Tabela 7.1 (b), a instrução bl delay (linhas 2 e 4)
salta para o endereço indicado pela label delay.
void delay(void) {
for (int i = 0; i < 100; ++i)
;
}
(a) |
1delay:
2 mov r0, #100
3 mov r1, #0
4for:
5 cmp r1, r0
6 bcc for_end
7 add r1, r1, #1
8 b for
9for_end:
10 mov pc, lr
(b) |
Na função delay a variável local i tem o âmbito do for. Pode ser suportada num registo do processador. No caso do programa da Tabela 7.2 (b), é suportada no registo R1.
Figura 7.1 Ilustração de chamada a função sem parâmetros¶
Na primeira chamada, no endereço 0x4306, o processador começa
por transferir o conteúdo de PC (0x4308) para LR
e em seguida afeta o PC com o endereço de delay (0x4076).
Na segunda chamada, no endereço 0x430e, o processador realiza ações semelhantes,
com a diferença do valor de LR ser 0x4310.
Ao executar a instrução mov pc, lr posicionada no final da função,
no endereço 0x4082, o processamento regressa ao endereço 0x4308
na primeira chamada e regressa ao endereço 0x4310
na segunda chamada.
7.2. Função com parâmetros¶
Considere-se a sequência de duas chamadas à função multiply com a seguinte assinatura:
uint16_t multiply(uint8_t multiplying, uint8_t multiplier);
...
product[2] = multiply(4, 10);
product[3] = multiply(8, 10);
...
(a) |
1mov r0, #4
2mov r1, #10
3bl multiply
4str r0, [r4, #4]
5mov r0, #8
6mov r1, #12
7bl multiply
8str r0, [r4, #6]
(b) |
A função multiply tem dois parâmetros – multiplying e multiplier ambos do tipo uint8_t – e retorna um valor do tipo uint16_t. Na fase de chamada, antes da execução de bl é necessário passar os argumentos. O que corresponde a colocar os valores dos argumentos no local que dá suporte aos parâmetros. Nesta função utilizam-se o registo R0 para passar o primeiro argumento e o registo R1 para passar o segundo argumento.
No programa (b) da Tabela 7.3, na primeira chamada, os argumentos 4 e 10, são carregados em R0 e R1 (linhas 1 e 2), respetivamente; na segunda chamada os argumentos 8 e 12, são carregados em R0 e R1 (linhas 5 e 6), respetivamente.
uint16_t multiply(uint8_t multiplying,
uint8_t multiplier) {
uint16_t product = 0;
while ( multiplier > 0 ) {
product += multiplying;
multiplier--;
}
return product;
}
(a) |
1multiply:
2 mov r2, #0
3while:
4 sub r1, r1, #0
5 beq while_end
6 add r2, r2, r0
7 sub r1, r1, #1
8 b while
9while_end:
10 mov r0, r2
11 mov pc, lr
(b) |
No programa (b) da Tabela 7.4 assume-se que os registos de suporte aos parâmetros – R0 e R1 – já contêm os argumentos. A variável local product como não prevalece para além do âmbito desta função é suportada no registo R2, entre as linhas 2 e 10. O valor de retorno da função – o resultado da multiplicação – é depositado no registo R0 (linha 10).
Figura 7.2 Ilustração de chamada a função com parâmetros¶
Na Figura 7.2 a instrução mov pc, lr, no endereço 0x8984,
faz o processador retornar ao endereço 0x8a08 na primeira chamada
e ao endereço 0x8a10 na segunda chamada.
Nestas posições estão as instruções str r0,[r4, ...]
para guardar o valor retornado pela função multiply
que vem no registo R0.