INF1018 - Software Básico (2014.1)
Segundo Trabalho

Gerador de Funções

O objetivo deste trabalho é implementar em C uma função gera_func que recebe como parâmetros uma função f e a descrição de um conjunto de parâmetros e retorna uma "nova versão" de f, flinha, gerada dinamicamente que, ao ser chamada, chama a função original da maneira definida em gera_func. Os argumentos passados a gera_func podem fazer com que flinha já tenha valores constantes "amarrados" a um ou mais dos argumentos de f, e também definem a posição em que os argumentos a serem usados na chamada a f aparecerão na chamada a flinha (os parâmetros são numerados da esquerda para a direita na chamada da função).

Para pensar em um exemplo, considere a função pow, da biblioteca matemática, que retorna o valor de seu primeiro parâmetro (x) elevado ao segundo (y):

   double pow (double x, double y);
A função gera_func nos permite criar dinamicamente uma nova função que sempre eleva o seu parâmetro ao quadrado. Para criar essa função, gera_func amarra o segundo parâmetro de pow, construindo, em tempo de execução, o código de uma função que chama pow com dois parâmetros: o primeiro é seu próprio parâmetro e o segundo é o valor 2.0. Quando pow retornar, essa função retorna o valor retornado por pow.

Também deve ser implementada uma função libera_func, que é responsável por liberar a função criada dinamicamente por gera_func.

Leia com atenção o enunciado do trabalho e as instruções de entrega.

Em caso de dúvidas, não invente. Pergunte!


Especificação das funções:

A função gera_func deve ter o seguinte protótipo:

   void* gera_func (void* f, int n, Parametro params[]);
onde f tem o endereço da função a ser chamada, o array params contém a lista de parâmetros de f e n é o número de parâmetros descritos por params.

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 amarrado;    /* indica se o parâmetro deve ter um valor amarrado */
      int posicao; /* indica em que posição esse parâmetro vai aparecer na chamada da função gerada */
      union {
         int v_int;
         char v_char;
         double v_double;
         void* v_ptr;
      } valor;         /* define o valor do parâmetro se este for amarrado */
   } Parametro;

A função libera_func deve ter o protótipo a seguir:

   void libera_func (void* func);

O arquivo gera_func.h contém as definições acima, e pode ser pego AQUI. O trabalho deve seguir estritamente as definições constantes nesse arquivo.


Exemplos de uso:

O programa abaixo usa gera_func para criar dinamicamente uma função que eleva números ao quadrado e depois invoca essa função para obter os quadrados de 1 a 10:
   #include <math.h>
   #include <stdio.h>
   #include "gera_func.h"

   typedef double (*func_ptr) (double x);

   int main (void) {
      Parametro params[2];
      func_ptr f_quadrado = NULL;
      double d;
      int i;

      params[0].tipo = DOUBLE_PAR;
      params[0].amarrado = 0;
      params[0].posicao = 1;
      params[1].tipo = DOUBLE_PAR;
      params[1].amarrado = 1;
      params[1].valor.v_double = 2.0;

      f_quadrado = (func_ptr) gera_func (pow, 2, params);

      for (i = 1; i <= 10; i++) {
         d = f_quadrado(i);
         printf("%d ^ 2 = %f\n", i, d);
      }

      libera_func(f_quadrado);
      return 0;
   }


Como segundo exemplo, o código abaixo usa gera_func para criar uma nova função, foo, que chama a função boo, definida no mesmo arquivo, invertendo os argumentos passados:
#include <stdio.h>
#include <string.h>
#include "gera_func.h"

int boo (int a, int b) {
  return a-b;
}

typedef int (*func_ptr) (int x, int y);

int main (void) {
  Parametro params[2];
  func_ptr foo = NULL;
  int a, b;

  params[0].tipo = INT_PAR;
  params[0].amarrado = 0;
  params[0].posicao = 2;
  params[1].tipo = INT_PAR;
  params[1].amarrado = 0;
  params[1].posicao = 1;

  foo = gera_func (boo, 2, params);
  a = 2; b = 3;
  printf ("boo(%d,%d) = %d\n", a, b, boo(a,b));
  printf ("foo(%d,%d) = %d\n", a, b, foo(a,b));

  return 0;
}


Ainda um outro exemplo é o uso da função de comparação de bytes memcmp, da biblioteca padrão de C, para determinar se diferentes strings são prefixos de uma determinada string fixa. A função memcmp recebe como argumento duas strings e um número n e verifica se os n primeiros bytes das duas são iguais:
int memcmp(const void *s1, const void *s2, size_t n);
Podemos usar a função gera_func para fixar uma das strings, passando para a nova função apenas a string que queremos testar e o tamanho do prefixo que desejamos testar:
#include <stdio.h>
#include <string.h>
#include "gera_func.h"

typedef int (*func_ptr) (void* candidata, size_t n);

char a[] = "quero saber se a outra string é um prefixo dessa aqu";

int main (void) {
  Parametro params[3];
  func_ptr e_prefixo = NULL;
  char um[] = "quero saber se por acaso falei alguma bobagem";
  int tam;

  params[0].tipo = PTR_PAR;
  params[0].amarrado = 0;
  params[0].posicao = 1;
  params[1].tipo = PTR_PAR;
  params[1].amarrado = 1;
  params[1].valor.v_ptr = a;
  params[2].tipo = INT_PAR;
  params[2].amarrado = 0;
  params[2].posicao = 2;

  e_prefixo = (func_ptr) gera_func (memcmp, 3, params);

  tam = 3;
  printf ("'%s' e' prefixo-%d de '%s'? %s\n", um, tam, a,e_prefixo (um, tam)?"NAO":"SIM");
  tam = 15;
  printf ("%s e' prefixo-%d de '%s'? %s\n", um, tam, a,e_prefixo (um, tam)?"NAO":"SIM");
  tam = 25;
  printf ("'%s' e' prefixo-%d de '%s'? %s\n", um, tam, a,e_prefixo (um, tam)?"NAO":"SIM");

  return 0;
}



Implementação:

A função gera_func deve ser implementada em C, mas ela deve gerar, em um bloco de memória alocado, um trecho de código em linguagem de máquina que implementa a função criada dinamicamente. O valor de retorno de gera_func será um ponteiro para esse bloco de memória.

De forma geral, gera_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 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 assembly contendo uma função simples (o retorno do seu parâmetro, por exemplo) usando:
    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 teste a geração de código para chamar uma função que retorna o valor de seu parâmetro inteiro, sem fixar e depois 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 13 de junho 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: