【问题标题】:How to read the content of a file to a string in C?如何将文件的内容读入C中的字符串?
【发布时间】:2010-09-15 12:41:56
【问题描述】:

用 C 语言打开文件并将其内容读入字符串(char*、char[] 等)最简单的方法是什么(最不容易出错,最少的代码行,但你想解释它)?

【问题讨论】:

  • “最简单的方式”和“最不容易出错”往往是对立的。
  • “最简单的方式”和“最不容易出错”在我的书中实际上是同义词。例如,C# 中的答案是string s = File.ReadAllText(filename);。这怎么可能更简单,更容易出错?

标签: c string file


【解决方案1】:

我倾向于将整个缓冲区作为原始内存块加载到内存中并自己进行解析。这样我就可以最好地控制标准库在多个平台上的作用。

这是我为此使用的存根。您可能还想检查 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...
}

【讨论】:

  • 我还会检查 fread 的返回值,因为它可能由于错误而不会真正读取整个文件。
  • 就像 rmeador 说的,fseek 将在大于 4GB 的文件上失败。
  • 是的。对于大文件,这个解决方案很糟糕。
  • 由于这是一个登录页面,我想指出fread 不会以零结尾您的字符串。这可能会导致一些麻烦。
  • 正如@Manbroski 所说,缓冲区需要被“\0”终止。所以我会更改 buffer = malloc (length + 1); 并在 fclose 之后添加:buffer[length] = '\0';(由 Valgrind 验证)
【解决方案2】:

另一个不幸的是高度依赖于操作系统的解决方案是内存映射文件。好处通常包括读取性能,以及减少内存使用,因为应用程序查看和操作系统文件缓存实际上可以共享物理内存。

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() 提供。

【讨论】:

  • 别忘了检查这些系统调用的返回值!
  • 在调用 lseek() 时必须使用 off_t 而不是 int。
  • 请注意,如果目标是在给定时刻稳定地在内存中捕获文件的内容,则应避免使用此解决方案,除非您确定正在读入内存的文件不会在使用地图的时间间隔内被其他进程修改。请参阅此post 了解更多信息。
【解决方案3】:

如果“将其内容读入字符串”意味着文件不包含代码为 0 的字符,您还可以使用 getdelim() 函数,该函数要么接受一块内存并在必要时重新分配它,要么只分配整个缓冲区,并将文件读入其中,直到遇到指定的分隔符或文件结尾。只需传递 '\0' 作为分隔符即可读取整个文件。

此函数在 GNU C 库中可用,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 */

【讨论】:

  • 我以前用过这个!假设您正在阅读的文件是文本(不包含 \0),它的效果非常好。
  • 不错!在整个文本文件中啜饮时节省了很多问题。现在,如果有一种类似的超简单方法可以读取二进制文件流直到 EOF,而不需要任何分隔符!
【解决方案4】:

如果您正在读取标准输入或管道等特殊文件,您将无法事先使用 fstat 获取文件大小。此外,如果您正在读取二进制文件,fgets 会因为嵌入的 '\0' 字符而丢失字符串大小信息。读取文件的最佳方法是使用 read 和 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;
}

【讨论】:

  • 这是 O(n^2),其中 n 是文件的长度。投票数超过此值的所有解决方案都是 O(n)。请不要在实践中使用此解决方案,或使用具有乘法增长的修改版本。
  • realloc() 可以将现有内存扩展到新的大小,而无需将旧内存复制到更大的新内存。只有当有对 malloc() 的干预调用时,它才需要移动内存并使这个解决方案 O(n^2)。在这里,在调用 realloc() 之间没有调用 malloc(),所以解决方案应该没问题。
  • 您可以直接读入“str”缓冲区(具有适当的偏移量),而无需从中间“buf”中复制。然而,该技术通常会过度分配文件内容所需的内存。还要注意二进制文件,printf 不会正确处理它们,而且你可能不想打印二进制文件!
【解决方案5】:

如果文件是文本,并且要逐行获取文本,最简单的方法是使用 fgets()。

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

【讨论】:

    【解决方案6】:

    注意:这是对上面接受的答案的修改。

    这是一种方法,包括错误检查。

    我添加了一个大小检查器以在文件大于 1 GiB 时退出。我这样做是因为程序将整个文件放入一个字符串中,这可能会使用过多的内存并使计算机崩溃。但是,如果您不关心这一点,您可以将其从代码中删除。

    #include <stdio.h>
    #include <stdlib.h>
    
    #define FILE_OK 0
    #define FILE_NOT_EXIST 1
    #define FILE_TOO_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_TOO_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);
    }
    

    【讨论】:

    • 只有一个问题:您分配给malloc(length +1)buffer 没有被释放。这是这个方法的消费者应该做的事情,还是不需要free()分配的内存?
    • 如果没有发生错误,free(f_data);应该调用。感谢您指出这一点
    • 你在FILE_TO_LARGE中拼错了“too”
    【解决方案7】:

    如果你使用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);
      }
    }
    

    【讨论】:

      【解决方案8】:

      刚刚从上面接受的答案修改。

      #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 九年前我在编辑时对这个问题的反应如此迅速!虽然函数部分是纯 C,但我很抱歉我的 will-not-run-on-c 答案。
      • 这个古老的问题被列在活跃问题的顶部。我没有搜索它。
      • 这段代码会泄漏内存,别忘了释放你的 malloc 内存 :)
      【解决方案9】:
      // 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;
      }
      

      这是一个非常粗略的解决方案,因为没有针对 null 进行任何检查。

      【讨论】:

      • 这仅适用于基于磁盘的文件。对于命名管道、标准输入或网络流,它将失败。
      • 哈,也是我来这里的原因!但我认为您需要 null 终止字符串,或者返回 glShaderSource 可选的长度。
      【解决方案10】:

      我将根据这里的答案添加我自己的版本,仅供参考。我的代码考虑了 sizeof(char) 并添加了一些 cmets。

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

      【讨论】:

        【解决方案11】:

        用 C 语言打开文件并将其内容读入字符串的最简单方法是什么(最不容易出错,最少的代码行,但你想解释它)...?

        遗憾的是,多年后的答案很容易出错,而且许多答案都缺乏正确的 string 格式。

        #include <stdio.h>
        #include <stdlib.h>
        
        // Read the file into allocated memory.
        // Return NULL on error.
        char* readfile(FILE *f) {
          // f invalid? fseek() fail?
          if (f == NULL || fseek(f, 0, SEEK_END)) {
            return NULL;
          }
        
          long length = ftell(f);
          rewind(f);
          // Did ftell() fail?  Is the length too long?
          if (length == -1 || (unsigned long) length >= SIZE_MAX) {
            return NULL;
          }
        
          // Convert from long to size_t
          size_t ulength = (size_t) length;
          char *buffer = malloc(ulength + 1);
          // Allocation failed? Read incomplete?
          if (buffer == NULL || fread(buffer, 1, ulength, f) != ulength) {
            free(buffer);
            return NULL;
          }
          buffer[ulength] = '\0'; // Now buffer points to a string
        
          return buffer;
        }
        

        请注意,如果文本文件包含空字符,则分配的数据将包含所有文件数据,但字符串会显得很短。更好的代码还会返回长度信息,以便调用者可以处理。

        char* readfile(FILE *f, size_t *ulength_ptr) {
          ...
          if (ulength_ptr) *ulength_ptr == *ulength;
          ...
        } 
        

        【讨论】:

          【解决方案12】:

          简单整洁(假设文件内容少于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;
          }
          

          【讨论】:

          • 请不要预先分配您认为需要的所有内存。这是糟糕设计的完美例子。只要有可能,您应该随时分配内存。如果您希望文件长 10,000 字节,这将是一个很好的设计,您的程序无法处理任何其他大小的文件,并且您正在检查大小并出错,但这不是这里发生的事情。你真的应该学习如何正确地编写 C 代码。
          猜你喜欢
          • 2011-02-24
          • 2011-08-31
          • 2020-08-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多