如何在C中将文件的内容读取为字符串?


96

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


8
“最简单的方式”和“最容易出错”常常是彼此相反的。
安迪·莱斯特,

14
在我的书中,“最简单的方法”和“容易出错的错误”实际上是同义词。例如,C#的答案是string s = File.ReadAllText(filename);。这怎么可能更简单,更容易出错?
Mark Lakata 2014年

Answers:


145

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

这是我使用的存根。您可能还需要检查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,

31
由于这是一个登录页面,我想指出的是,该fread字符串不会零结尾。这可能会导致一些麻烦。
ivan-k 2014年

18
正如@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 '18

3
调用lseek()时必须使用off_t而不是int。
ivan.ukr

1
请注意,如果目标是在给定的时间段内稳定地将文件内容捕获到内存中,则应避免使用此解决方案,除非您确定正在读取的文件在此间隔内不会被其他进程修改将在其上使用地图。有关更多信息,请参见此帖子
user001

12

如果“将其内容读入字符串”表示该文件不包含代码为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 */

1
我以前用过!假设您正在读取的文件是文本(不包含\ 0),它会很好地工作。
迅速

不错!拖入整个文本文件时,可以节省很多问题。现在,如果有一种类似的非常简单的方法来读取二进制文件流,直到EOF,而无需任何定界字符!
安东尼

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预先获取文件大小。另外,如果您正在读取二进制文件,由于嵌入的'\ 0'字符,fgets将会丢失字符串大小信息。读取文件的最佳方法是使用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;
}

1
这是O(n ^ 2),其中n是文件的长度。投票数比其更多的所有解都是O(n)。请不要在实践中使用此解决方案,或使用具有可乘性增长的修改版本。
Clark Gaebel '16

2
realloc()可以将现有内存扩展到新大小,而无需将旧内存复制到新的更大内存中。只有在对malloc()进行中间调用时,才需要移动内存并使该解决方案变为O(n ^ 2)。在这里,在对realloc()的调用之间没有发生对malloc()的调用,因此解决方案应该没问题。
杰克

2
您可以直接读取“ str”缓冲区(具有适当的偏移量),而无需从中间“ buf”进行复制。但是,这种技术通常会过度分配文件内容所需的内存。还要注意二进制文件,printf将无法正确处理它们,并且您可能还是不想打印二进制文件!
安东尼

3

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

这是完成错误检查的一种方法。

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

#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) {
                 *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
}

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

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

这是一个非常粗糙的解决方案,因为没有检查是否存在null。


这仅适用于基于磁盘的文件。对于命名管道,标准输入或网络流,它将失败。
安东尼

哈,也是我为什么来这里!但是我认为您需要使字符串终止为null或返回glShaderSource可选的长度。
西罗Santilli郝海东冠状病六四事件法轮功

1

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

#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语言,但是我对“不会在C语言上运行”的答案感到抱歉。
BaiJiFeiLong

这个古老的问题列在活动问题的顶部。我没有搜寻。
Gerhardh

此代码会泄漏内存,别忘了释放您的malloc内存:)
ericcurtin

0

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

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

简单易用(假设文件中的内容少于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。
杰克·吉芬
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.