INF1018- Software Básico - 2011.2

Segundo Trabalho: Gerador de Funções


Descrição do trabalho

O objetivo deste trabalho é implementar uma função cria_func que recebe como parâmetros uma função f e a descrição de seus parâmetros e retorna um ponteiro para uma nova função, gerada dinamicamente, que, ao ser chamada, só precisa do valor dos argumentos que não foram "fixados" por 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 sempre multiplicasse seu único parâmetro por um número fixo (por exemplo, 2).

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) o número 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 permanecem como parâmetros da nova função (gerada por cria_func).

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 e n é o número de parâmetros.

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.


Alguns exemplos

Considere que 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.

Um outro exemplo é uma implementação genérica de BubbleSort que recebe como argumento, além do array a ser ordenado, uma função para a comparação de dois elementos. O arquivo gensort.c mostra um exemplo de implementação genérica, e de funções que poderiam ser usadas como argumento. A função cria_func poderia ser usada para gerar versões dessa implementação genérica para ordenação em ordem crescente ou decrescente, sendo essa ordem definida fixando-se a função de comparação.


Implementação

A função cria_func deve alocar um bloco de memória para escrever o código em linguagem de máquina que implementa a função criada dinamicamente. O valor de retorno de cria_func será um ponteiro para essa área. De forma geral, cria_func irá percorrer o array com a descrição dos parâmetros e gerar um código em linguagem de máquina que:

Recomendamos fortemente que você faça uma implementação gradual, desenvolvendo e testando passo-a-passo cada nova funcionalidade implementada. Comece, por exemplo, com um esqueleto que aloca espaço para o código a ser gerado, coloca um código bem conhecido nessa região e retorna o endereço da região alocada. Teste a chamada a essa função gerada dinamicamente. Para obter um código "bem conhecido" você pode compilar um arquivo C (ou assembly) contendo uma função simples (o retorno do seu parâmetro, por exemplo) usando:

minhamaquina> gcc -O0 -c code.c
ou
minhamaquina> gcc -m32 -c code.s
A opção -c é usada para compilar e não gerar o executável. Depois de compilar, veja o código de máquina gerado usando:
minhamaquina> objdump -d code.o
A seguir, por exemplo, implemente e e teste a geração de código para chamada a uma função que retorna o valor de seu parâmetro inteiro, sem fixar e fixando o valor desse parâmetro. Vá depois aumentando o número e os tipos de parâmetros tratados. Use os exemplos dados no enunciado do trabalho mas faça também testes mais completos!


Prazo

O trabalho deve ser entregue até meia-noite do dia 21 de novembro por mensagem eletrônica.

Será descontado um ponto por cada dia de atraso (incluindo sábado, domingo e feriados).


Entrega

Leia com atenção as intruções para entrega a seguir:


Observações