Como ler o conteúdo de um arquivo para uma string em C?
96
Qual é a maneira mais simples (menos propensa a erros, menos linhas de código, no entanto você deseja interpretá-lo) para abrir um arquivo em C e ler seu conteúdo em uma string (char *, char [], seja o que for)?
"maneira mais simples" e "menos propensa a erros" costumam ser opostos.
Andy Lester,
14
"maneira mais simples" e "menos suscetível a erros" são, na verdade, sinônimos em meu livro. Por exemplo, a resposta em C # é string s = File.ReadAllText(filename);. Como isso poderia ser mais simples e mais sujeito a erros?
Mark Lakata,
Respostas:
145
Eu tendo a carregar o buffer inteiro como um pedaço de memória bruto na memória e fazer a análise por conta própria. Dessa forma, tenho melhor controle sobre o que a lib padrão faz em várias plataformas.
Este é um esboço que uso para isso. você também pode querer verificar os códigos de erro para fseek, ftell e fread. (omitido para maior clareza).
char* buffer =0;long length;FILE* f = fopen (filename,"rb");if(f){
fseek (f,0, SEEK_END);
length = ftell (f);
fseek (f,0, SEEK_SET);
buffer = malloc (length);if(buffer){
fread (buffer,1, length, f);}
fclose (f);}if(buffer){// start to process your data / extract strings here...}
Eu também verificaria o valor de retorno de fread, uma vez que ele pode não ler todo o arquivo devido a erros e quais não.
freespace
6
como o rmeador disse, o fseek falhará em arquivos> 4GB.
KPexEA
6
Verdade. Para arquivos grandes, essa solução é uma droga.
Nils Pipenbrinck
31
Como esta é uma página de destino, gostaria de salientar que freadsua string não termina em zero. Isso pode causar alguns problemas.
ivan-k,
18
Como @Manbroski disse, o buffer precisa ser encerrado com '\ 0'. Então, eu mudaria buffer = malloc (length + 1);e adicionaria após fclose: buffer[length] = '\0';(validado por Valgrind)
soywod
26
Outra solução, infelizmente altamente dependente do sistema operacional, é o mapeamento de memória do arquivo. Os benefícios geralmente incluem desempenho de leitura e uso reduzido de memória, pois a visualização dos aplicativos e o cache de arquivos dos sistemas operacionais podem realmente compartilhar a memória física.
O código POSIX ficaria assim:
int fd = open("filename", O_RDONLY);int len = lseek(fd,0, SEEK_END);void*data = mmap(0, len, PROT_READ, MAP_PRIVATE, fd,0);
O Windows, por outro lado, é um pouco mais complicado e, infelizmente, não tenho um compilador à minha frente para testar, mas a funcionalidade é fornecida por CreateFileMapping()e MapViewOfFile().
Não se esqueça de verificar os valores de retorno dessas chamadas de sistema!
Toby Speight
3
deve usar off_t ao invés de int ao chamar lseek ().
ivan.ukr
1
Observe que se o objetivo é capturar de forma estável na memória o conteúdo de um arquivo em um determinado momento, esta solução deve ser evitada, a menos que você tenha certeza de que o arquivo que está sendo lido na memória não será modificado por outros processos durante o intervalo sobre o qual o mapa será usado. Veja esta postagem para mais informações.
user001
12
Se "ler seu conteúdo em uma string" significa que o arquivo não contém caracteres com o código 0, você também pode usar a função getdelim (), que aceita um bloco de memória e o realoca se necessário, ou apenas aloca todo o buffer para você e lê o arquivo nele até encontrar um delimitador especificado ou o fim do arquivo. Basta passar '\ 0' como delimitador para ler todo o arquivo.
O código de amostra pode parecer tão simples quanto
char* buffer = NULL;size_t len;ssize_t bytes_read = getdelim(&buffer,&len,'\0', fp);if( bytes_read !=-1){/* Success, now the entire file is in the buffer */
Já usei isso antes! Funciona muito bem, assumindo que o arquivo que você está lendo é um texto (não contém \ 0).
epemiente
LEGAIS! Poupa muitos problemas ao slurping em arquivos de texto inteiros. Agora, se houvesse uma forma ultra simples semelhante de ler um fluxo de arquivo binário até EOF sem a necessidade de nenhum caractere delimitador!
anthony
6
Se o arquivo for de texto e você quiser obter o texto linha por linha, a maneira mais fácil é usar fgets ().
char buffer[100];FILE*fp = fopen("filename","r");// do not use "rb"while(fgets(buffer,sizeof(buffer), fp)){...do something
}
fclose(fp);
Se você estiver lendo arquivos especiais como stdin ou pipe, não poderá usar fstat para obter o tamanho do arquivo de antemão. Além disso, se você estiver lendo um arquivo binário, fgets perderá as informações de tamanho da string por causa dos caracteres '\ 0' incorporados. A melhor maneira de ler um arquivo é usar ler e realocar:
#include<stdio.h>#include<unistd.h>#include<errno.h>#include<string.h>int main (){char buf[4096];ssize_t n;char*str = NULL;size_t len =0;while(n = read(STDIN_FILENO, buf,sizeof buf)){if(n <0){if(errno == EAGAIN)continue;
perror("read");break;}
str = realloc(str, len + n +1);
memcpy(str + len, buf, n);
len += n;
str[len]='\0';}
printf("%.*s\n", len, str);return0;}
Este é O (n ^ 2), onde n é o comprimento do seu arquivo. Todas as soluções com mais votos positivos são O (n). Não use esta solução na prática, ou use uma versão modificada com crescimento multiplicativo.
Clark Gaebel
2
realloc () pode estender a memória existente para o novo tamanho sem copiar a memória antiga para um novo pedaço maior de memória. somente se houver chamadas intermediárias para malloc () ele precisará mover a memória e tornar esta solução O (n ^ 2). aqui, não há chamadas para malloc () que acontecem entre as chamadas para realloc (), portanto, a solução deve ser adequada.
Jake
2
Você pode ler diretamente no buffer "str" (com um deslocamento apropriado), sem precisar copiar de um "buf" intermediário. Essa técnica, entretanto, geralmente aloca em excesso a memória necessária para o conteúdo do arquivo. Também tome cuidado com os arquivos binários, o printf não os tratará corretamente e você provavelmente não deseja imprimir os binários de qualquer maneira!
anthony
3
Nota: Esta é uma modificação da resposta aceita acima.
Esta é uma maneira de fazer isso, completa com verificação de erros.
Eu adicionei um verificador de tamanho para encerrar quando o arquivo for maior que 1 GiB. Fiz isso porque o programa coloca o arquivo inteiro em uma string que pode usar muita memória RAM e travar o computador. No entanto, se você não se importar com isso, basta removê-lo do código.
#include<stdio.h>#include<stdlib.h>#define FILE_OK 0#define FILE_NOT_EXIST 1#define FILE_TO_LARGE 2#define FILE_READ_ERROR 3char* c_read_file(constchar* f_name,int* err,size_t* f_size){char* buffer;size_t length;FILE* f = fopen(f_name,"rb");size_t read_length;if(f){
fseek(f,0, SEEK_END);
length = ftell(f);
fseek(f,0, SEEK_SET);// 1 GiB; best not to load a whole large file in one stringif(length >1073741824){*err = FILE_TO_LARGE;return NULL;}
buffer =(char*)malloc(length +1);if(length){
read_length = fread(buffer,1, length, f);if(length != read_length){*err = FILE_READ_ERROR;return NULL;}}
fclose(f);*err = FILE_OK;
buffer[length]='\0';*f_size = length;}else{*err = FILE_NOT_EXIST;return NULL;}return buffer;}
E para verificar se há erros:
int err;size_t f_size;char* f_data;
f_data = c_read_file("test.txt",&err,&f_size);if(err){// process error}
// Assumes the file exists and will seg. fault otherwise.constGLchar*load_shader_source(char*filename){FILE*file = fopen(filename,"r");// open
fseek(file,0L, SEEK_END);// find the endsize_t size = ftell(file);// get the size in bytesGLchar*shaderSource = calloc(1, size);// allocate enough bytes
rewind(file);// go back to file beginning
fread(shaderSource, size,sizeof(char), file);// read each char into ourblock
fclose(file);// close the streamreturn shaderSource;}
Esta é uma solução bastante rudimentar porque nada é verificado em relação ao nulo.
Este não é um código C. A pergunta não está marcada como C ++.
Gerhardh
@Gerhardh Resposta tão rápida à pergunta há nove anos, quando estou editando! Embora a parte da função seja C puro, sinto muito por minha resposta não funcionará em c.
BaiJiFeiLong
Esta pergunta antiga estava listada no topo das perguntas ativas. Eu não procurei por isso.
Gerhardh
Este código vaza memória, não se esqueça de liberar sua memória malloc'd :)
ericcurtin
0
Adicionarei minha própria versão, com base nas respostas aqui, apenas para referência. Meu código leva em consideração sizeof (char) e adiciona alguns comentários a ele.
// Open the file in read mode.FILE*file = fopen(file_name,"r");// Check if there was an error.if(file == NULL){
fprintf(stderr,"Error: Can't open file '%s'.", file_name);
exit(EXIT_FAILURE);}// Get the file length
fseek(file,0, SEEK_END);long length = ftell(file);
fseek(file,0, SEEK_SET);// Create the string for the file contents.char*buffer = malloc(sizeof(char)*(length +1));
buffer[length]='\0';// Set the contents of the string.
fread(buffer,sizeof(char), length, file);// Close the file.
fclose(file);// Do something with the data.// ...// Free the allocated string space.
free(buffer);
Por favor, não aloque toda a memória que você acha que precisará antecipadamente. Este é um exemplo perfeito de design ruim. Você deve alocar memória conforme o uso, sempre que possível. Seria um bom design se você esperasse que o arquivo tivesse 10.000 bytes, seu programa não pudesse lidar com um arquivo de qualquer outro tamanho e você estivesse verificando o tamanho e errando de qualquer maneira, mas não é isso que está acontecendo aqui. Você realmente deve aprender a codificar C corretamente.
string s = File.ReadAllText(filename);
. Como isso poderia ser mais simples e mais sujeito a erros?Respostas:
Eu tendo a carregar o buffer inteiro como um pedaço de memória bruto na memória e fazer a análise por conta própria. Dessa forma, tenho melhor controle sobre o que a lib padrão faz em várias plataformas.
Este é um esboço que uso para isso. você também pode querer verificar os códigos de erro para fseek, ftell e fread. (omitido para maior clareza).
fonte
fread
sua string não termina em zero. Isso pode causar alguns problemas.buffer = malloc (length + 1);
e adicionaria após fclose:buffer[length] = '\0';
(validado por Valgrind)Outra solução, infelizmente altamente dependente do sistema operacional, é o mapeamento de memória do arquivo. Os benefícios geralmente incluem desempenho de leitura e uso reduzido de memória, pois a visualização dos aplicativos e o cache de arquivos dos sistemas operacionais podem realmente compartilhar a memória física.
O código POSIX ficaria assim:
O Windows, por outro lado, é um pouco mais complicado e, infelizmente, não tenho um compilador à minha frente para testar, mas a funcionalidade é fornecida por
CreateFileMapping()
eMapViewOfFile()
.fonte
Se "ler seu conteúdo em uma string" significa que o arquivo não contém caracteres com o código 0, você também pode usar a função getdelim (), que aceita um bloco de memória e o realoca se necessário, ou apenas aloca todo o buffer para você e lê o arquivo nele até encontrar um delimitador especificado ou o fim do arquivo. Basta passar '\ 0' como delimitador para ler todo o arquivo.
Esta função está disponível na GNU C Library, http://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getdelim-994
O código de amostra pode parecer tão simples quanto
fonte
Se o arquivo for de texto e você quiser obter o texto linha por linha, a maneira mais fácil é usar fgets ().
fonte
Se você estiver lendo arquivos especiais como stdin ou pipe, não poderá usar fstat para obter o tamanho do arquivo de antemão. Além disso, se você estiver lendo um arquivo binário, fgets perderá as informações de tamanho da string por causa dos caracteres '\ 0' incorporados. A melhor maneira de ler um arquivo é usar ler e realocar:
fonte
Nota: Esta é uma modificação da resposta aceita acima.
Esta é uma maneira de fazer isso, completa com verificação de erros.
Eu adicionei um verificador de tamanho para encerrar quando o arquivo for maior que 1 GiB. Fiz isso porque o programa coloca o arquivo inteiro em uma string que pode usar muita memória RAM e travar o computador. No entanto, se você não se importar com isso, basta removê-lo do código.
E para verificar se há erros:
fonte
Se estiver usando
glib
, você pode usar g_file_get_contents ;fonte
Esta é uma solução bastante rudimentar porque nada é verificado em relação ao nulo.
fonte
glShaderSource
opcionalmente leva.Apenas modificado a partir da resposta aceita acima.
fonte
Adicionarei minha própria versão, com base nas respostas aqui, apenas para referência. Meu código leva em consideração sizeof (char) e adiciona alguns comentários a ele.
fonte
fácil e organizado (assumindo que o conteúdo do arquivo seja inferior a 10.000):
fonte