INF1612 - Software Básico - 2006.1

Segundo Trabalho

Gerador de Funções

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:

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.

Prazo

IMPORTANTE:

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.o
Construa um programa inicial que aloque espaço e coloque esse código "colado" do compilador, bem conhecido, para testar um esqueleto inicial.

Observações: