====== Arquivos binários ====== {{ progc_arquivos_binarios.mkv |Video desta aula}} Todos os arquivos contêm sequências de bytes, mas costuma-se dizer que um arquivo é "binário" quando seu conteúdo não é uma informação textual (ou seja, não representa um texto usando codificações como ASCII, UTF-8 ou outras). Arquivos binários são usados para armazenar informações mais complexas como imagens, música, código executável, etc. Em C, um arquivo binário é visto como uma sequência de blocos de mesmo tamanho. O tamanho dos blocos depende do tipo de informação armazenada no arquivo. Por exemplo, um arquivo de números reais ''double'' terá blocos de 8 bytes, enquanto um arquivo de caracteres (''char'') terá blocos de 1 byte, como mostra a figura: {{ binary-files.png |Blocos em arquivos binários}} Lembre-se que o SO só armazena a sequência de bytes, sem considerar nem registrar o tamanho dos blocos. **Cabe à aplicação** definir o tamanho de bloco que deseja usar em cada arquivo. ==== Leitura/escrita de blocos ==== A linguagem C oferece funções para ler e escrever blocos de bytes em arquivos, que efetuam a cópia desses bytes da memória para o arquivo ou vice-versa. A funções a seguir permitem ler/escrever blocos de bytes em arquivos binários. Todas essas funções estão definidas no arquivo ''stdio.h''. Lê até ''count'' blocos de tamanho ''size'' bytes cada um e os deposita no vetor ''data'', a partir do //stream// indicado. Retorna o número de blocos lidos: size_t fread (void* data, size_t size, size_t count, FILE* stream) Escreve até ''count'' blocos de tamanho ''size'' bytes do vetor ''data'' no //stream// indicado. Retorna o número de blocos escritos: size_t fwrite (const void* data, size_t size, size_t count, FILE* stream) Essas funções também podem ser usadas para ler/escrever em arquivos-texto, pois textos são sequências de blocos de 1 byte. ==== Exemplo de uso ==== Este exemplo manipula um arquivo binário ''numeros.dat'' contendo números reais. São implementadas (em arquivos separados) as operações de escrita de números no arquivo, listagem e ordenação do conteúdo: // Escreve N valores reais aleatórios em um arquivo, em formato binário #include #include #include #define ARQUIVO "numeros.dat" #define NUMVAL 10 int main (int argc, char *argv[]) { FILE* arq ; int i, ret ; float value[NUMVAL] ; // abre o arquivo em modo "append" arq = fopen (ARQUIVO, "a") ; if (!arq) { perror ("Erro ao abrir arquivo") ; exit (1) ; } // inicia gerador de números aleatórios srand (clock()) ; // gera NUMVAL valores aleatórios reais for (i = 0; i < NUMVAL; i++) value[i] = random() / 100000.0 ; // escreve os valores gerados no final do arquivo ret = fwrite (value, sizeof (float), NUMVAL, arq) ; if (ret) printf ("Gravou %d valores com sucesso!\n", ret) ; else printf ("Erro ao gravar...\n") ; // fecha o arquivo fclose (arq) ; return (0) ; } // Lista o conteúdo de um arquivo que contém números reais em formato binário #include #include #define ARQUIVO "numeros.dat" int main (int argc, char *argv[]) { FILE* arq ; float value ; // abre o arquivo em modo leitura arq = fopen (ARQUIVO, "r") ; if (!arq) { perror ("Erro ao abrir arquivo") ; exit (1) ; } // lê e imprime os valores contidos no arquivo fread (&value, sizeof (float), 1, arq) ; while (! feof (arq)) { printf ("%f\n", value) ; fread (&value, sizeof (float), 1, arq) ; } // fecha o arquivo fclose (arq) ; return (0) ; } // Lê os números reais de um arquivo binário, os ordena e os escreve de volta no arquivo #include #include #define ARQUIVO "numeros.dat" #define MAXVAL 100000 float value[MAXVAL] ; int num_values ; int main (int argc, char *argv[]) { FILE* arq ; int i, j, menor ; float aux ; // abre o arquivo em leitura/escrita, preservando o conteúdo arq = fopen (ARQUIVO, "r+") ; if (!arq) { perror ("Erro ao abrir arquivo") ; exit (1) ; } // lê números do arquivo no vetor num_values = fread (value, sizeof (float), MAXVAL, arq) ; printf ("Encontrei %d números no arquivo\n", num_values) ; // ordena os números (por seleção) for (i = 0; i < num_values-1; i++) { // encontra o menor elemento no restante do vetor menor = i ; for (j = i+1; j < num_values; j++) if (value[j] < value[menor]) menor = j ; // se existe menor != i, os troca entre si if (menor != i) { aux = value[i] ; value[i] = value[menor] ; value[menor] = aux ; } } // retorna o ponteiro ao início do arquivo rewind (arq) ; // escreve números do vetor no arquivo fwrite (value, sizeof (float), num_values, arq) ; // fecha o arquivo fclose (arq) ; return (0) ; } No arquivo ''ordena.c'', o conteúdo inteiro do arquivo é lido com **apenas uma** chamada ''fread'' e escrito com apenas uma chamada ''fwrite''. Isso é perfeitamente possível, desde que a estrutura usada para receber os dados na memória coincida byte a byte com a forma como eles estão dispostos no arquivo. ===== Posicionamento no arquivo ===== Para cada arquivo aberto em uma aplicação, o sistema operacional mantém um contador interno indicando a posição da próxima operação de leitura ou escrita. Esse contador é conhecido como **ponteiro de posição** (embora não seja realmente um ponteiro). Por default, as operações em um arquivo em C ocorrem em posições sucessivas dentro do arquivo: cada leitura (ou escrita) corre **após** a leitura (ou escrita) precedente, até atingir o final do arquivo. Essa forma de acesso ao arquivo é chamada de **acesso sequencial**. Por vezes uma aplicação precisa ler ou escrever em posições específicas de um arquivo, ou precisa voltar a ler uma posição do arquivo que já percorreu anteriormente. Isso ocorre frequentemente em aplicações que manipulam arquivos muito grandes, como vídeos ou bases de dados. Para isso é necessária uma forma de **acesso direto** a posições específicas do arquivo. Em C, o acesso direto a posições específicas de um arquivo é feita através de funções de **posicionamento de ponteiro**, que permitem alterar o valor do ponteiro de posição do arquivo, antes da operação de leitura/escrita desejada. Todas as funções de manipulação do ponteiro de arquivo consideram as **posições em bytes** a partir do início do arquivo, nunca em número de blocos. As funções mais usuais para acessar o ponteiro de posição de um arquivo em C são indicadas a seguir. Ajusta posição do ponteiro no //stream// indicado: int fseek (FILE* stream, long int offset, int whence) O ajuste é definido por ''offset'', enquanto o valor de ''whence'' indica se o ajuste é relativo ao início do arquivo (''SEEK_SET''), à posição corrente (''SEEK_CUR'') ou ao final do arquivo (''SEEK_END''). Ver também ''fseeko'' e ''fseeko64''. Exemplos: // posiciona o ponteiro de "arq": fseek (arq, 1000, SEEK_SET) ; // 1000 bytes após o início fseek (arq, -300, SEEK_END) ; // 300 bytes antes do fim fseek (arq, -500, SEEK_CUR) ; // 500 bytes antes da posição atual Reposiciona o ponteiro no início (posição 0) do //stream// indicado: void rewind (FILE* stream) Informa a posição corrente de leitura/escrita em ''stream'' (ver também ''ftello'' e ''ftello64'' no manual). long int ftell (FILE* stream) ===== Outras funções ===== Para truncar ("encurtar") um arquivo, deixando somente os primeiros ''length'' bytes: #include #include // usando o nome do arquivo, sem abri-lo int truncate (const char *path, off_t length); // usando um descritor UNIX (fd) int ftruncate (int fd, off_t length); Para obter as propriedades (metadados) de um arquivo: #include #include #include // usando o nome do arquivo, sem abri-lo int stat (const char *pathname, struct stat *statbuf); // usando um descritor UNIX (fd) int fstat (int fd, struct stat *statbuf); As informações sobre o arquivo serão depositadas pelo núcleo na estrutura ''statbuf'', cujo conteúdo está descrito abaixo. Os campos da estrutura são detalhados na página de manual da função ''fstat()''. struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* Inode number */ mode_t st_mode; /* File type and mode */ nlink_t st_nlink; /* Number of hard links */ uid_t st_uid; /* User ID of owner */ gid_t st_gid; /* Group ID of owner */ dev_t st_rdev; /* Device ID (if special file) */ off_t st_size; /* Total size, in bytes */ blksize_t st_blksize; /* Block size for filesystem I/O */ blkcnt_t st_blocks; /* Number of 512B blocks allocated */ struct timespec st_atim; /* Time of last access */ struct timespec st_mtim; /* Time of last modification */ struct timespec st_ctim; /* Time of last status change */ #define st_atime st_atim.tv_sec /* For backward compatibility */ #define st_mtime st_mtim.tv_sec #define st_ctime st_ctim.tv_sec }; ===== Exercícios ===== - Escreva três programas C separados para: - escrever um arquivo com n (n é um número aleatório > 100) inteiros ''long'' aleatórios, armazenados em modo binário; - ler o arquivo de inteiros em um vetor, ordenar o vetor e reescrever o arquivo; - escrever na tela os primeiros 10 números e os últimos 10 números contidos no arquivo. - Utilize stat ou fstat para recuperar o tamanho do arquivo Mais exercícios no capítulo 11 da {{apostila_c_-_nce.pdf|apostila do NCE/UFRJ}}.