O objetivo deste trabalho é implementar uma função cria_func
que recebe como parâmetros uma função f
e mais alguns argumentos
para f
e retorna uma nova função que, ao ser chamada,
só precisa dos argumentos não passados para cria_func
.
Como exemplo, imagine que temos uma função:
int mult (int a, int b) { return a*b; }
A função cria_func
nos permitiria criar uma "nova versão" de mult
que multiplicasse sempre por um número fixo, por exemplo, 2.
(Exemplos mais interessantes vão aparecer mais adiante.)
Dizemos que cria_func
amarra alguns valores de parâmetros
de f
.
Para isso, cria_func
deve receber como parâmetros
uma função f
qualquer (um ponteiro de função em C)
e uma descrição da lista dos parâmetros de f
,
contendo os tipos dos parâmetros, e, no caso em que se deseja amarrar
valores, os valores.
A função cria_func
deve então gerar uma nova função
que, quando chamada, vai chamar f
passando como parâmetros
os valores que tiverem sido amarrados na sua criação.
Os parâmetros de f
que não tiveram valores amarrados
na criação da nova função permanecem como parâmetros da nova função.
A função cria_func
deve ter o seguinte protótipo:
void* cria_func (void* f, int n, Parametro params[]);onde o array
params
contém a lista de parâmetros
de f
.
O tipo Parametro
é definido como:
typedef enum {INT_PAR, CHAR_PAR, DOUBLE_PAR, PTR_PAR} TipoParam; typedef struct { TipoParam tipo; /* indica o tipo do parâmetro */ int e_constante; /* indica se o parâmetro deve ter um valor constante */ union { int v_int; char v_char; double v_double; void* v_ptr; } valor; /* define o valor do parâmetro se este for constante */ } Parametro;Também deve ser implementada a função
void libera_func (void* func);que é responsável por liberar uma função criada dinamicamente.
Por exemplo, seja f
a função
double pow (double x, double y);o trecho de programa a seguir cria dinamicamente uma função que eleva números ao quadrado:
#include <math.h> #include <stdlib.h> #include "cria_func.h" typedef double (*func_ptr) (double); int main (void) { Parametro params[2]; func_ptr f_quadrado = NULL; double d; int i; params[0].tipo = DOUBLE_PAR; params[0].e_constante = 0; params[1].tipo = DOUBLE_PAR; params[1].e_constante = 1; params[1].valor.v_double = 2.0; f_quadrado = (func_ptr) cria_func (pow, 2, params); for (i = 1; i <= 10; i++) { d = f_quadrado(i); printf("%d ^ 2 = %f\n", i, d); } return 0; }Nesse exemplo,
cria_func
cria em tempo de execução uma nova
função baseada na função pow
, que amarra o parâmetro
y
com o valor 2.0 e deixa o parâmetro x
livre
para ser definido posteriormente.
Desta forma, essa nova função deve receber um parâmetro double
(correspondente ao parâmetro x
de pow
)
e retorna um outro valor double
como resultado.
Como o parâmetro y
ficou amarrado ao valor 2.0,
f_quadrado
sempre retorna o quadrado do valor de seu argumento.
A função cria_func
deve ser implementada em C,
mas ela deve gerar, em alguma área de memória, um trecho de código em
linguagem de máquina que implementa a função criada dinamicamente.
De forma geral, cria_func
irá percorrer o vetor com a descrição
dos parâmetros e gerar um código em linguagem de máquina que:
push
para empilhar os parâmetros
para a função original, respeitando os tipos e eventuais valores constantes
especificados no vetor params
;
call
;
Um exemplo mais interessante e uso de cria_func
é dado por uma
implementação genérica de Quicksort, que recebe como argumentos, além
do array a ser ordenado, duas funções, uma para escolha do pivô e outra para
comparação de dois elementos.
O arquivo genquicksort.c mostra um exemplo
de implementação genérica e de algumas funções que poderiam ser usadas
como argumento.
A função cria_func
poderia ser usada para gerar versões
simplificadas dessa implementação genérica, onde uma ou duas dessas
funções estivessem fixas.
Esse trabalho é bastante grande e complexo. Tente construir seu programa em passos pequenos para poder testar cada parte separadamente. Por exemplo, compile um arquivo C contendo uma função simples usando:
minhamaquina> gcc -O2 -c code.c(para apenas compilar e não gerar o executável) e depois veja o código de máquina gerado usando:
minhamaquina> objdump -d code.oConstrua um programa inicial que aloque espaço e coloque esse código "colado" do compilador, bem conhecido, para testar um esqueleto inicial.
cria_func.h
pode ser pego
AQUI.
O trabalho deve seguir estritamente as definições constantes nesse arquivo.
objdump
será provavelmente a maior fonte de referência.
Os opcodes das instruções em linguagem de máquina que devem ser
geradas dinamicamente também podem ser obtidos
no Intel Pentium Instruction Set Reference Manual (Volume 2).
cria_func
e libera_func
devem ser implementadas em um módulo independente.
Nomeie o arquivo
de implementação do seu módulo como cria_func.c
.
cria_func.c
.