Jaki jest najprostszy sposób (najmniej podatny na błędy, najmniej wierszy kodu, jakkolwiek chcesz go zinterpretować), aby otworzyć plik w C i wczytać jego zawartość do łańcucha (char *, char [], cokolwiek)?
„Najprostsza droga” i „najmniej podatna na błędy” są często przeciwieństwami.
Andy Lester
15
„Najprostsza droga” i „najmniej podatna na błędy” są w mojej książce synonimami. Na przykład odpowiedź w C # to string s = File.ReadAllText(filename);. Jak to może być prostsze i bardziej podatne na błędy?
Mark Lakata,
Odpowiedzi:
146
Mam tendencję do ładowania całego bufora jako surowego fragmentu pamięci do pamięci i przeprowadzania analizy samodzielnie. W ten sposób mam najlepszą kontrolę nad tym, co robi standardowa biblioteka na wielu platformach.
To jest odcinek, którego używam do tego. możesz również chcieć sprawdzić kody błędów dla fseek, ftell i fread. (pominięte dla jasności).
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...
}
Sprawdziłbym również wartość zwracaną przez fread, ponieważ może on nie odczytać całego pliku z powodu błędów, a co nie.
wolna przestrzeń
6
jak powiedział rmeador, fseek zakończy się niepowodzeniem w przypadku plików> 4 GB.
KPexEA
6
Prawdziwe. W przypadku dużych plików to rozwiązanie jest do bani.
Nils Pipenbrinck
33
Ponieważ jest to strona docelowa, chciałbym zaznaczyć, że freadnie kończy ona zerowego ciągu. Może to prowadzić do problemów.
ivan-k
19
Jak powiedział @Manbroski, bufor musi zostać zakończony '\ 0'. Więc zmieniłbym buffer = malloc (length + 1);i dodał po fclose: buffer[length] = '\0';(zatwierdzone przez Valgrind)
soywod
26
Innym, niestety silnie zależnym od systemu operacyjnego, rozwiązaniem jest mapowanie pliku w pamięci. Korzyści ogólnie obejmują wydajność odczytu i mniejsze użycie pamięci, ponieważ widok aplikacji i pamięć podręczna plików systemu operacyjnego mogą faktycznie współdzielić pamięć fizyczną.
Kod POSIX wyglądałby tak:
int fd = open("filename", O_RDONLY);
int len = lseek(fd, 0, SEEK_END);
void *data = mmap(0, len, PROT_READ, MAP_PRIVATE, fd, 0);
Z drugiej strony Windows jest trochę trudniejszy i niestety nie mam przed sobą kompilatora do przetestowania, ale funkcjonalność zapewnia CreateFileMapping()i MapViewOfFile().
Nie zapomnij sprawdzić wartości zwracanych przez te wywołania systemowe!
Toby Speight
3
musi używać off_t zamiast int podczas wywoływania lseek ().
ivan.ukr
1
Zwróć uwagę, że jeśli celem jest stabilne utrwalenie w pamięci zawartości pliku w danym momencie, tego rozwiązania należy unikać, chyba że masz pewność, że plik wczytywany do pamięci nie zostanie zmodyfikowany przez inne procesy w tym interwale nad którym mapa będzie używana. Zobacz ten post, aby uzyskać więcej informacji.
user001
13
Jeśli „przeczytaj jego zawartość w ciągu znaków” oznacza, że plik nie zawiera znaków z kodem 0, możesz również użyć funkcji getdelim (), która albo akceptuje blok pamięci i ponownie go przydziela, jeśli to konieczne, lub po prostu przydziela cały bufor dla ty, i wczytuje do niego plik, aż napotka określony separator lub koniec pliku. Po prostu podaj „\ 0” jako separator, aby odczytać cały plik.
Używałem tego wcześniej! Działa bardzo ładnie, zakładając, że czytany plik jest tekstowy (nie zawiera \ 0).
ephemient
ŁADNY! Oszczędza wiele problemów podczas siorbania w całych plikach tekstowych. Teraz, gdyby istniał podobny, bardzo prosty sposób odczytu strumienia plików binarnych do EOF bez potrzeby stosowania znaku ograniczającego!
anthony
6
Jeśli plik jest tekstowy i chcesz uzyskać tekst wiersz po wierszu, najłatwiejszym sposobem jest użycie fgets ().
char buffer[100];
FILE *fp = fopen("filename", "r"); // do not use "rb"while (fgets(buffer, sizeof(buffer), fp)) {
... do something
}
fclose(fp);
Jeśli czytasz specjalne pliki, takie jak stdin lub potok, nie będziesz w stanie użyć fstat do wcześniejszego ustalenia rozmiaru pliku. Ponadto, jeśli czytasz plik binarny, fgets utraci informacje o rozmiarze ciągu z powodu osadzonych znaków „\ 0”. Najlepszym sposobem na odczytanie pliku jest użycie funkcji read i realloc:
#include<stdio.h>#include<unistd.h>#include<errno.h>#include<string.h>intmain(){
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;
}
To jest O (n ^ 2), gdzie n to długość twojego pliku. Wszystkie rozwiązania z większą liczbą głosów pozytywnych to O (n). Nie używaj tego rozwiązania w praktyce ani nie używaj zmodyfikowanej wersji z multiplikatywnym wzrostem.
Clark Gaebel
2
realloc () może rozszerzyć istniejącą pamięć do nowego rozmiaru bez kopiowania starej pamięci do nowego, większego fragmentu pamięci. tylko wtedy, gdy występują interweniujące wywołania funkcji malloc (), będzie ona musiała poruszyć pamięć i sprawić, by to rozwiązanie było O (n ^ 2). tutaj nie ma żadnych wywołań funkcji malloc (), które występują między wywołaniami funkcji realloc (), więc rozwiązanie powinno być w porządku.
Jake,
2
Można było czytać bezpośrednio do bufora „str” (z odpowiednim przesunięciem), bez konieczności kopiowania z pośredniego „buf”. Jednak ta technika generalnie powoduje nadmierne przydzielanie pamięci potrzebnej na zawartość pliku. Uważaj również na pliki binarne, printf nie będzie ich poprawnie obsługiwać, a prawdopodobnie i tak nie chcesz drukować plików binarnych!
anthony
4
Uwaga: jest to modyfikacja zaakceptowanej odpowiedzi powyżej.
Oto sposób, aby to zrobić, wraz ze sprawdzaniem błędów.
Dodałem sprawdzanie rozmiaru, aby zakończyć, gdy plik był większy niż 1 GiB. Zrobiłem to, ponieważ program umieszcza cały plik w ciągu, który może zużywać zbyt dużo pamięci RAM i spowodować awarię komputera. Jeśli jednak Cię to nie obchodzi, możesz po prostu usunąć go z kodu.
#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;
returnNULL;
}
buffer = (char *)malloc(length + 1);
if (length) {
read_length = fread(buffer, 1, length, f);
if (length != read_length) {
free(buffer);
*err = FILE_READ_ERROR;
returnNULL;
}
}
fclose(f);
*err = FILE_OK;
buffer[length] = '\0';
*f_size = length;
}
else {
*err = FILE_NOT_EXIST;
returnNULL;
}
return buffer;
}
Aby sprawdzić błędy:
int err;
size_t f_size;
char * f_data;
f_data = c_read_file("test.txt", &err, &f_size);
if (err) {
// process error
}
else {
// process datafree(f_data);
}
Tylko jedno pytanie: ten buffer, z którym zostałeś przydzielony malloc(length +1), nie zostanie uwolniony. Czy jest to coś, co powinien zrobić konsument tej metody, czy też nie ma potrzeby free()przydzielania pamięci?
Pablosproject
jeśli nie wystąpił błąd, wolne (f_data); należy zadzwonić. dzięki za zwrócenie uwagi
To nie jest kod C. Pytanie nie jest oznaczone jako C ++.
Gerhardh
@Gerhardh Tak szybka odpowiedź na pytanie dziewięć lat temu, kiedy edytuję! Chociaż część funkcji jest czystym C, przepraszam za moją odpowiedź, która nie chce działać na c.
BaiJiFeiLong
To starożytne pytanie zostało wymienione na początku aktywnych pytań. Nie szukałem tego.
Gerhardh
1
Ten kod przecieka pamięć, nie zapomnij uwolnić pamięci malloc'd :)
ericcurtin
1
// Assumes the file exists and will seg. fault otherwise.const GLchar *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 bytes
GLchar *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;
}
To dość prymitywne rozwiązanie, ponieważ nic nie jest sprawdzane pod kątem wartości null.
Będzie to możliwe tylko w przypadku plików dyskowych. Nie powiedzie się dla nazwanych potoków, standardowego wejścia lub strumieni sieciowych.
anthony
Ha, też dlaczego tu przyjechałem! Ale myślę, że musisz albo zerować null, albo zwrócić długość, która glShaderSourceopcjonalnie przyjmuje.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
0
Dodam własną wersję, opartą na odpowiedziach tutaj, tylko w celach informacyjnych. Mój kod bierze pod uwagę sizeof (char) i dodaje do niego kilka komentarzy.
// 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);
Nie przydzielaj z góry całej pamięci , której potrzebujesz. To doskonały przykład złego projektu. Pamięć należy przydzielać na bieżąco, gdy tylko jest to możliwe. Byłby dobry projekt, gdybyś oczekiwał, że plik będzie miał 10 000 bajtów, twój program nie może obsłużyć pliku o jakimkolwiek innym rozmiarze, a Ty i tak sprawdzasz rozmiar i mylisz się. Naprawdę powinieneś nauczyć się poprawnie kodować C.
string s = File.ReadAllText(filename);
. Jak to może być prostsze i bardziej podatne na błędy?Odpowiedzi:
Mam tendencję do ładowania całego bufora jako surowego fragmentu pamięci do pamięci i przeprowadzania analizy samodzielnie. W ten sposób mam najlepszą kontrolę nad tym, co robi standardowa biblioteka na wielu platformach.
To jest odcinek, którego używam do tego. możesz również chcieć sprawdzić kody błędów dla fseek, ftell i fread. (pominięte dla jasności).
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... }
źródło
fread
nie kończy ona zerowego ciągu. Może to prowadzić do problemów.buffer = malloc (length + 1);
i dodał po fclose:buffer[length] = '\0';
(zatwierdzone przez Valgrind)Innym, niestety silnie zależnym od systemu operacyjnego, rozwiązaniem jest mapowanie pliku w pamięci. Korzyści ogólnie obejmują wydajność odczytu i mniejsze użycie pamięci, ponieważ widok aplikacji i pamięć podręczna plików systemu operacyjnego mogą faktycznie współdzielić pamięć fizyczną.
Kod POSIX wyglądałby tak:
int fd = open("filename", O_RDONLY); int len = lseek(fd, 0, SEEK_END); void *data = mmap(0, len, PROT_READ, MAP_PRIVATE, fd, 0);
Z drugiej strony Windows jest trochę trudniejszy i niestety nie mam przed sobą kompilatora do przetestowania, ale funkcjonalność zapewnia
CreateFileMapping()
iMapViewOfFile()
.źródło
Jeśli „przeczytaj jego zawartość w ciągu znaków” oznacza, że plik nie zawiera znaków z kodem 0, możesz również użyć funkcji getdelim (), która albo akceptuje blok pamięci i ponownie go przydziela, jeśli to konieczne, lub po prostu przydziela cały bufor dla ty, i wczytuje do niego plik, aż napotka określony separator lub koniec pliku. Po prostu podaj „\ 0” jako separator, aby odczytać cały plik.
Ta funkcja jest dostępna w bibliotece GNU C, http://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getdelim-994
Przykładowy kod może wyglądać tak prosto jak
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 */
źródło
Jeśli plik jest tekstowy i chcesz uzyskać tekst wiersz po wierszu, najłatwiejszym sposobem jest użycie fgets ().
char buffer[100]; FILE *fp = fopen("filename", "r"); // do not use "rb" while (fgets(buffer, sizeof(buffer), fp)) { ... do something } fclose(fp);
źródło
Jeśli czytasz specjalne pliki, takie jak stdin lub potok, nie będziesz w stanie użyć fstat do wcześniejszego ustalenia rozmiaru pliku. Ponadto, jeśli czytasz plik binarny, fgets utraci informacje o rozmiarze ciągu z powodu osadzonych znaków „\ 0”. Najlepszym sposobem na odczytanie pliku jest użycie funkcji read i realloc:
#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); return 0; }
źródło
Uwaga: jest to modyfikacja zaakceptowanej odpowiedzi powyżej.
Oto sposób, aby to zrobić, wraz ze sprawdzaniem błędów.
Dodałem sprawdzanie rozmiaru, aby zakończyć, gdy plik był większy niż 1 GiB. Zrobiłem to, ponieważ program umieszcza cały plik w ciągu, który może zużywać zbyt dużo pamięci RAM i spowodować awarię komputera. Jeśli jednak Cię to nie obchodzi, możesz po prostu usunąć go z kodu.
#include <stdio.h> #include <stdlib.h> #define FILE_OK 0 #define FILE_NOT_EXIST 1 #define FILE_TO_LARGE 2 #define FILE_READ_ERROR 3 char * c_read_file(const char * 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 string if (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) { free(buffer); *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; }
Aby sprawdzić błędy:
int err; size_t f_size; char * f_data; f_data = c_read_file("test.txt", &err, &f_size); if (err) { // process error } else { // process data free(f_data); }
źródło
buffer
, z którym zostałeś przydzielonymalloc(length +1)
, nie zostanie uwolniony. Czy jest to coś, co powinien zrobić konsument tej metody, czy też nie ma potrzebyfree()
przydzielania pamięci?Jeśli używasz
glib
, możesz użyć g_file_get_contents ;gchar *contents; GError *err = NULL; g_file_get_contents ("foo.txt", &contents, NULL, &err); g_assert ((contents == NULL && err != NULL) || (contents != NULL && err == NULL)); if (err != NULL) { // Report error to user, and free error g_assert (contents == NULL); fprintf (stderr, "Unable to read file: %s\n", err->message); g_error_free (err); } else { // Use file contents g_assert (contents != NULL); } }
źródło
Właśnie zmodyfikowano z zaakceptowanej odpowiedzi powyżej.
#include <stdio.h> #include <stdlib.h> #include <assert.h> char *readFile(char *filename) { FILE *f = fopen(filename, "rt"); assert(f); fseek(f, 0, SEEK_END); long length = ftell(f); fseek(f, 0, SEEK_SET); char *buffer = (char *) malloc(length + 1); buffer[length] = '\0'; fread(buffer, 1, length, f); fclose(f); return buffer; } int main() { char *content = readFile("../hello.txt"); printf("%s", content); }
źródło
// Assumes the file exists and will seg. fault otherwise. const GLchar *load_shader_source(char *filename) { FILE *file = fopen(filename, "r"); // open fseek(file, 0L, SEEK_END); // find the end size_t size = ftell(file); // get the size in bytes GLchar *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 stream return shaderSource; }
To dość prymitywne rozwiązanie, ponieważ nic nie jest sprawdzane pod kątem wartości null.
źródło
glShaderSource
opcjonalnie przyjmuje.Dodam własną wersję, opartą na odpowiedziach tutaj, tylko w celach informacyjnych. Mój kod bierze pod uwagę sizeof (char) i dodaje do niego kilka komentarzy.
// 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);
źródło
łatwe i zgrabne (zakładając, że zawartość pliku jest mniejsza niż 10000):
void read_whole_file(char fileName[1000], char buffer[10000]) { FILE * file = fopen(fileName, "r"); if(file == NULL) { puts("File not found"); exit(1); } char c; int idx=0; while (fscanf(file , "%c" ,&c) == 1) { buffer[idx] = c; idx++; } buffer[idx] = 0; }
źródło