Linguagens e Ambientes de Programação (2016/2017)



Teórica 15 (02/mai/2017)

Pré-processador.
Polimorfismo implementado usando mecanismos de baixo nível.
Módulos genéricos (polimórficos) em C.



Pré-processador

Programa que corre sempre antes do compilador de C e que faz substituição de macros, inclusão de ficheiros, e possibilita compilação condicional.

Em Linux, o pré-processador chama-se cpp e pode ser invocado diretamente pelo utilizador. Mas é mais prático deixar que seja o compilador de C a invocar o pré-processador automaticamente. Em todo o caso, vejamos o que diz o início do manual do comando cpp:

Exemplos de macros:

Exemplos de inclusão de ficheiros

Exemplos de compilação condicional

No caso das macros com argumentos, note que existe a necessidade de proteger todas as utilizações dos argumentos com parêntesis, para compensar o facto da expansão das macros ser textual. Por exemplo, considere a seguinte chamada mult(1+1,5) no caso da macro abaixo não seguir esta regra...

Polimorfismo implementado usando mecanismos de baixo nível

Função monomórfica

A seguinte função permite trocar os valores de duas variáveis inteiras. É uma função monomórfica pois os seus argumentos são de tipo fixo. Esta função pode ser testada assim: A função swap é segura pois o compilador valida todas as suas utilizações.

Polimorfismo implementado usando manipulação direta de memória

A seguinte função permite trocar os valores de duas variáveis de qualquer tipo. É uma função polimórfica pois os seus argumentos podem ser aplicados a argumentos de tipos diversos. Esta função ser testada assim: A função swap não é segura pois o compilador não tem a possibilidade de validar as chamadas.

Polimorfismo implementado usando macros

A seguinte macro também permite trocar os valores de duas variáveis de qualquer tipo. Esta macro ser testada assim: A macro swap é segura pois o compilador consegue detetar qualquer erro de tipo na sua utilização. Repare que o tipo dos valores a trocar é passado como parâmetro.

Repare nos cuidados que é preciso ter para escrever uma macro que não dê problemas. Os argumentos devem ser envolvidos em parêntesis, e qualquer nova variável que seja introduzido deve ter um nome que não entre em conflito com possíveis nomes existentes. Porque todos estes cuidados? E porque razão a macro foi definida usando um do-while? (Elimine o do-while, teste, e logo descobrirá o problema.)

Polimorfismo implementado usando manipulação direta de memória e macros

A seguinte implementação da operação swap é a mais agradável de usar do ponto de vista sintático. Até parece que está em causa uma operação primitiva da linguagem. Contido este implementação não é segura, pelo que o utilizador tem de ter algumas cautelas. Esta função ser testada assim: A macro swap não é segura pois o compilador não tem a possibilidade de validar as chamadas.

Em GCC

O pré-processador do GCC possui uma construção typeof que permite escrever macros ainda mais sofisticadas do que as anteriores e seguras. Por exemplo, a expressão typeof(a) representa o tipo da variável a e pode ser usada como um tipo normal, inclusivamente para definir outras variáveis com o mesmo tipo de a.

É pena o C padrão não suportar a construção typeof.

Exemplo de polimorfismo na biblioteca padrão do C - função qsort

A função qsort permite ordenar vetores de qualquer tipo.

Exercício: Como faria para ordenar um vetor com componentes do seguinte tipo:



Módulos genéricos (polimórficos) em C

É possível escrever módulos genéricos em C usando macros com várias linhas e a operação de concatenação de tokens que se escreve ##.

Os módulos genéricos são muito difíceis de escrever e de ler, mas são fáceis de usar.

No seguinte módulo, o tipo Data passa a ser argumento de duas macros.

Ficheiro Generic_LinkedList.h

Ficheiro Generic_LinkedList.c

Exemplo de instanciação do módulo genérico

Ficheiro LinkedList_double.h

Ficheiro LinkedList_double.c



#80