จะอ่านเนื้อหาของไฟล์เป็นสตริงใน C ได้อย่างไร?


97

อะไรคือวิธีที่ง่ายที่สุด (มีข้อผิดพลาดน้อยที่สุดโค้ดน้อยที่สุด แต่คุณต้องการตีความ) เพื่อเปิดไฟล์ใน C และอ่านเนื้อหาในสตริง (char *, char [], อะไรก็ได้)?


9
"วิธีที่ง่ายที่สุด" และ "เกิดข้อผิดพลาดน้อยที่สุด" มักเป็นสิ่งที่ตรงกันข้ามกัน
Andy Lester

15
"วิธีที่ง่ายที่สุด" และ "ข้อผิดพลาดน้อยที่สุด" มีความหมายเหมือนกันในหนังสือของฉัน ยกตัวอย่างเช่นคำตอบใน C # string s = File.ReadAllText(filename);คือ มันจะง่ายกว่าและเกิดข้อผิดพลาดได้อย่างไร?
Mark Lakata

คำตอบ:


146

ฉันมักจะโหลดบัฟเฟอร์ทั้งหมดเป็นหน่วยความจำดิบลงในหน่วยความจำและทำการแยกวิเคราะห์ด้วยตัวเอง ด้วยวิธีนี้ฉันสามารถควบคุมสิ่งที่ lib มาตรฐานทำบนหลายแพลตฟอร์มได้ดีที่สุด

นี่คือต้นขั้วที่ฉันใช้สำหรับสิ่งนี้ คุณอาจต้องการตรวจสอบรหัสข้อผิดพลาดสำหรับ fseek, ftell และ fread (ละไว้เพื่อความชัดเจน).

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...
}

3
ฉันจะตรวจสอบค่าส่งคืนของ fread เนื่องจากอาจไม่ได้อ่านทั้งไฟล์เนื่องจากข้อผิดพลาดและสิ่งที่ไม่จริง
อวกาศ

6
เหมือนที่ rmeador กล่าวว่า fseek จะล้มเหลวในไฟล์> 4GB
KPexEA

6
จริง. สำหรับไฟล์ขนาดใหญ่การแก้ปัญหานี้ไม่ดี
Nils Pipenbrinck

33
เนื่องจากนี่เป็นหน้า Landing Page ฉันจึงต้องการชี้ให้เห็นว่าfreadไม่มีการยุติสตริงของคุณเป็นศูนย์ ซึ่งอาจนำไปสู่ปัญหาบางอย่าง
ivan-k

19
ดังที่ @Manbroski กล่าวว่าบัฟเฟอร์ต้องถูกยกเลิก '\ 0' ดังนั้นฉันจะเปลี่ยนbuffer = malloc (length + 1);และเพิ่มหลังจาก fclose: buffer[length] = '\0';(ตรวจสอบโดย Valgrind)
soywod

26

อีกวิธีหนึ่งที่น่าเสียดายที่ขึ้นอยู่กับระบบปฏิบัติการอย่างมากคือการแมปไฟล์หน่วยความจำ ประโยชน์โดยทั่วไปรวมถึงประสิทธิภาพของการอ่านและการใช้หน่วยความจำที่ลดลงเนื่องจากมุมมองแอปพลิเคชันและแคชไฟล์ของระบบปฏิบัติการสามารถแชร์หน่วยความจำกายภาพได้

รหัส POSIX จะมีลักษณะดังนี้:

int fd = open("filename", O_RDONLY);
int len = lseek(fd, 0, SEEK_END);
void *data = mmap(0, len, PROT_READ, MAP_PRIVATE, fd, 0);

ในทางกลับกัน Windows นั้นยุ่งยากกว่าเล็กน้อยและน่าเสียดายที่ฉันไม่มีคอมไพเลอร์อยู่ตรงหน้าฉันเพื่อทดสอบ แต่ฟังก์ชันนี้มีให้โดยCreateFileMapping()และMapViewOfFile().


3
อย่าลืมตรวจสอบค่าส่งคืนจากการเรียกระบบเหล่านั้น!
Toby Speight

3
ต้องใช้ off_t แทน int เมื่อเรียก lseek ()
ivan.ukr

1
โปรดทราบว่าหากเป้าหมายคือการจับเนื้อหาของไฟล์ในหน่วยความจำอย่างเสถียรในช่วงเวลาที่กำหนดควรหลีกเลี่ยงวิธีแก้ปัญหานี้เว้นแต่คุณจะมั่นใจว่าไฟล์ที่กำลังอ่านลงในหน่วยความจำจะไม่ถูกแก้ไขโดยกระบวนการอื่นในระหว่างช่วงเวลา ซึ่งจะใช้แผนที่ ดูโพสต์นี้สำหรับข้อมูลเพิ่มเติม
user001

13

หาก "อ่านเนื้อหาเป็นสตริง" หมายความว่าไฟล์ไม่มีอักขระที่มีรหัส 0 คุณยังสามารถใช้ฟังก์ชัน getdelim () ซึ่งยอมรับบล็อกหน่วยความจำและจัดสรรใหม่หากจำเป็นหรือเพียงแค่จัดสรรบัฟเฟอร์ทั้งหมดสำหรับ คุณและอ่านไฟล์เข้าไปจนกว่าจะพบตัวคั่นที่ระบุหรือจุดสิ้นสุดของไฟล์ เพียงแค่ส่ง '\ 0' เป็นตัวคั่นเพื่ออ่านไฟล์ทั้งหมด

ฟังก์ชันนี้มีอยู่ใน GNU C Library http://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getdelim-994

โค้ดตัวอย่างอาจดูเรียบง่ายเหมือน

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 */

1
ฉันเคยใช้มาก่อน! มันใช้งานได้ดีมากโดยสมมติว่าไฟล์ที่คุณกำลังอ่านเป็นข้อความ (ไม่มี \ 0)
ephemient

ดี! ช่วยประหยัดปัญหาได้มากเมื่อไฟล์ข้อความทั้งไฟล์คลาดเคลื่อน ตอนนี้หากมีวิธีง่ายๆที่คล้ายกันในการอ่านสตรีมไฟล์ไบนารีจนถึง EOF โดยไม่ต้องใช้อักขระคั่นใด ๆ !
anthony

6

หากไฟล์เป็นข้อความและคุณต้องการรับข้อความทีละบรรทัดวิธีที่ง่ายที่สุดคือใช้ fgets ()

char buffer[100];
FILE *fp = fopen("filename", "r");                 // do not use "rb"
while (fgets(buffer, sizeof(buffer), fp)) {
... do something
}
fclose(fp);

6

หากคุณกำลังอ่านไฟล์พิเศษเช่น stdin หรือไพพ์คุณจะไม่สามารถใช้ fstat เพื่อรับขนาดไฟล์ล่วงหน้าได้ นอกจากนี้หากคุณกำลังอ่านไฟล์ไบนารี fgets จะสูญเสียข้อมูลขนาดสตริงเนื่องจากอักขระ '\ 0' ที่ฝังอยู่ วิธีที่ดีที่สุดในการอ่านไฟล์คือใช้ read and 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;
}

1
นี่คือ O (n ^ 2) โดยที่ n คือความยาวของไฟล์ของคุณ โซลูชันทั้งหมดที่มีการโหวตมากกว่านี้คือ O (n) โปรดอย่าใช้วิธีแก้ปัญหานี้ในทางปฏิบัติหรือใช้เวอร์ชันที่แก้ไขแล้วซึ่งมีการเติบโตแบบทวีคูณ
Clark Gaebel

2
realloc () สามารถขยายหน่วยความจำที่มีอยู่ให้มีขนาดใหม่ได้โดยไม่ต้องคัดลอกหน่วยความจำเก่าไปยังหน่วยความจำใหม่ที่มีขนาดใหญ่ขึ้น เฉพาะในกรณีที่มีการแทรกแซงการโทรไปยัง malloc () จะต้องย้ายหน่วยความจำไปรอบ ๆ และสร้างโซลูชันนี้ O (n ^ 2) ที่นี่ไม่มีการเรียก malloc () ที่เกิดขึ้นระหว่างการเรียกไปที่ realloc () ดังนั้นวิธีแก้ปัญหาก็น่าจะดี
Jake

2
คุณสามารถอ่านลงในบัฟเฟอร์ "str" ​​ได้โดยตรง (พร้อมออฟเซ็ตที่เหมาะสม) โดยไม่จำเป็นต้องคัดลอกจาก "buf" ระดับกลาง อย่างไรก็ตามเทคนิคนั้นโดยทั่วไปจะจัดสรรหน่วยความจำที่จำเป็นสำหรับเนื้อหาไฟล์ ระวังไฟล์ไบนารีด้วยเพราะ printf จะจัดการไฟล์ไม่ถูกต้องและคุณคงไม่ต้องการพิมพ์ไบนารีอยู่ดี!
anthony

4

หมายเหตุ: นี่เป็นการแก้ไขคำตอบที่ยอมรับข้างต้น

นี่คือวิธีการดำเนินการพร้อมด้วยการตรวจสอบข้อผิดพลาด

ฉันได้เพิ่มตัวตรวจสอบขนาดเพื่อออกเมื่อไฟล์ใหญ่กว่า 1 GiB ฉันทำเช่นนี้เนื่องจากโปรแกรมทำให้ไฟล์ทั้งหมดเป็นสตริงซึ่งอาจใช้ ram มากเกินไปและทำให้คอมพิวเตอร์พัง อย่างไรก็ตามหากคุณไม่สนใจสิ่งนั้นคุณก็สามารถลบออกจากโค้ดได้

#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;
}

และเพื่อตรวจสอบข้อผิดพลาด:

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);
}

1
คำถามเพียงข้อเดียว: สิ่งที่bufferคุณจัดสรรให้malloc(length +1)ไม่ได้รับการปลดปล่อย เป็นสิ่งที่ผู้ใช้วิธีนี้จะต้องทำหรือไม่จำเป็นต้องfree()มีหน่วยความจำที่จัดสรร?
Pablosproject

หากข้อผิดพลาดไม่เกิดขึ้นฟรี (f_data); ควรเรียกว่า ขอบคุณที่ชี้ให้เห็น
Joe Cool

2

หากคุณใช้glibงานคุณสามารถใช้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);
  }
}

2

เพิ่งแก้ไขจากคำตอบที่ยอมรับข้างต้น

#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);
}

นี่ไม่ใช่รหัส C คำถามไม่ได้ติดแท็กเป็น C ++
Gerhardh

@Gerhardh ตอบคำถามอย่างรวดเร็วเมื่อเก้าปีที่แล้วเมื่อฉันกำลังแก้ไข! แม้ว่าส่วนของฟังก์ชั่นจะเป็น C ล้วน แต่ฉันขอโทษสำหรับคำตอบ will-not-run-on-c ของฉัน
BaiJiFeiLong

คำถามโบราณนี้ระบุไว้ที่ด้านบนของคำถามที่ใช้งานอยู่ ฉันไม่ได้ค้นหามัน
Gerhardh

1
รหัสนี้รั่วหน่วยความจำอย่าลืมเพิ่มหน่วยความจำ 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 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;
}

นี่เป็นวิธีแก้ปัญหาที่ค่อนข้างหยาบเนื่องจากไม่มีการตรวจสอบค่าว่าง


สิ่งนี้จะใช้ได้กับไฟล์ที่ใช้ดิสก์เท่านั้น จะล้มเหลวสำหรับไปป์ที่มีชื่ออินพุตมาตรฐานหรือสตรีมเครือข่าย
anthony

ฮ่า ๆ ทำไมฉันถึงมาที่นี่! แต่ฉันคิดว่าคุณต้องยกเลิกสตริงglShaderSourceว่างเปล่าหรือคืนค่าความยาวที่เลือกใช้
Ciro Santilli 郝海东冠状病六四事件法轮功

0

ฉันจะเพิ่มเวอร์ชันของตัวเองตามคำตอบที่นี่เพื่อการอ้างอิงเท่านั้น รหัสของฉันคำนึงถึงขนาดของ (ถ่าน) และเพิ่มความคิดเห็นเล็กน้อยลงไป

// 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);

0

ง่ายและเรียบร้อย (สมมติว่าเนื้อหาในไฟล์น้อยกว่า 10,000):

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;
}

โปรดอย่าจัดสรรหน่วยความจำทั้งหมดที่คุณคิดว่าคุณต้องการล่วงหน้า นี่เป็นตัวอย่างที่สมบูรณ์แบบของการออกแบบที่ไม่ดี คุณควรจัดสรรหน่วยความจำแบบพกพาทุกครั้งที่ทำได้ จะเป็นการออกแบบที่ดีถ้าคุณคาดว่าไฟล์จะมีความยาว 10,000 ไบต์โปรแกรมของคุณไม่สามารถจัดการไฟล์ที่มีขนาดอื่นได้และคุณกำลังตรวจสอบขนาดและเกิดข้อผิดพลาดอยู่ แต่นั่นไม่ใช่สิ่งที่เกิดขึ้นที่นี่ คุณควรเรียนรู้วิธีการเขียนโค้ด C อย่างถูกต้อง
Jack Giffin
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.