sábado, 1 de agosto de 2009

Como implementar um interpretador de brainfuck - parte 2

Depois de aberto o arquivo, deve-se extrair o seu conteúdo, usando funções como fgets(), fscanf() e fread(). Eu prefiro usar a função fread(), pois é a mais rápida das três e a única que pode extrair o conteúdo do arquivo inteiro de uma vez só, sem a necessidade de usar estruturas de repetição. A velocidade do processamento da função fread() pode não fazer diferença em arquivos pequenos, mas em arquivos grandes (~1 MB), as outras funções são tão lentas que podem levar horas, enquanto a fread() pode fazer o serviço na mesma velocidade, ou quase, que quando feita em arquivos pequenos.

Use a função fseek() para seguir até o fim do arquivo e colha o offset do fim do arquivo para uma variável chamada tamanho, usando a função ftell(). Após isso, use a fread() para ler o arquivo e passar seu conteúdo para uma variável, chamada fonte. No Windows de 32 bit, há um limite de ~2 MB para ser dividido para todas as variáveis estáticas e isso pode ser um problema se o arquivo que for aberto pelo nosso programa for maior do que isso. Uma solução, é declarar variáveis muito grandes de forma dinâmica, usando alocação de memória dinâmica, a qual se limita a quantidade de memória disponível no computador. Use a informação da variável tamanho na função malloc(), para indicar o tamanho a ser alocado para a variável fonte. Normalmente, multiplica-se o tamanho desejado pelo tamanho em bytes do tipo de dado usado, mas como estamos usando o tipo char, não é necessário fazer isso, já que a quantidade de bytes do tipo char é igual a 1. Para outros tipos, usa-se o seguinte modelo:

tipo *nome = (tipo *) malloc(tamanho * sizeof(tipo));

Segue um exemplo, onde se deseja alocar um vetor de 256 itens, do tipo int:

int *vetor = (int *) malloc(256 * sizeof(int));

Também, a conversão do retorno de malloc(), pelo fato do seu retorno ser do tipo void *, é opcional em C, mas é obrigatório em C++. Por isso, eu resolvi coloca-lo no código, para permitir compatibilidade.

Também deve verificar se o programa conseguiu alocar memória, pois se não conseguir alocar memória e mesmo assim tentar colocar algo na variável, haverá um problema e o programa terminará de forma abrupta. Use a função memset() para limpar a variável fonte. Também, a memória deve ser liberada, usando a função free(). Atualmente, a maioria dos sistemas operacionais liberam a memória automaticamente, após o programa terminar, mas é uma boa prática fazer isso manualmente (se uma variável dinâmica deixar de ser usada e o programa for realizar outras tarefas, é necessário a liberação de memória manualmente, senão a memória se esgotará e os programas deixarão de funcionar corretamente). Não se esqueça de incluir as bibliotecas stdlib.h e string.h. Ficará assim:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
   int c;
   FILE *arquivo; // Deve-se usar o asterisco aqui, por ser ponteiro
   if (argc >= 2) // Se tiver 2 ou mais argumentos
   {
      arquivo = fopen(argv[1], "rb"); // Abre o arquivo especificado
      if (arquivo == NULL) // Se não conseguir abrir o arquivo
      {
         puts("ERRO: Não foi possível abrir o arquivo!");
         do { c = getchar(); } while ((c != '\n') && (c != EOF));
         return 1; // Encerra o programa
      }
   }
   else
   {
      return 0; // Encerra o programa
   }

   fseek(arquivo, 0, SEEK_END);
   int tamanho = ftell(arquivo); // Offset do fim do arquivo, que equivale ao tamanho do arquivo
   rewind(arquivo); // Rebobina o arquivo

   // Aloca memória, com um byte adicional para o nulo (usado em strings em C)
   char *fonte = (char *) malloc(tamanho + 1);
   if (fonte == NULL) // Verifica se consegui alocar memoria
   {
      puts("ERRO: Não há memória o suficiente!");
      do { c = getchar(); } while ((c != '\n') && (c != EOF));
      return 1;
   }

   memset(fonte, 0, tamanho * sizeof(char)); // Preencherá a variável fonte com zeros (para "limpar")
   size_t size = fread(fonte, 1, tamanho, arquivo);
   fonte[tamanho] = 0; // Põe o sinal de fim (nulo)
   if (size != tamanho * sizeof(char))
   {
      puts("ERRO: Não foi possível ler o arquivo!");
      do { c = getchar(); } while ((c != '\n') && (c != EOF));
      return 1;
   }

   free(fonte);
   do { c = getchar(); } while ((c != '\n') && (c != EOF));
   return 0;
}

A função malloc() é usada para alocar a memória, sendo que precisa de um argumento especificando a quantidade de memória desejada. Se conseguir alocar memória, devolverá o endereço da memória disponível para fonte. Caso contrário, devolverá zero. O segundo argumento de fread() serve para indicar quantos bytes tem cada elemento do arquivo. Estamos usando ASCII, o que equivale a 1 byte por elemento. Esse número poderia ser maior se estivéssemos usando Unicode.

« parte anterior próxima parte »

Postagens relacionadas

0 comentários:

Postar um comentário