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().

Tabela 7.1 Chamada a função sem parâmetros.
...
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.

Tabela 7.2 Programa de função sem parâmetros.
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.

_images/bl1.png

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);

Tabela 7.3 Chamada de função com parâmetros
...
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.

Tabela 7.4 Programação de função com parâmetros
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).

_images/bl2.png

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.