在C语言中将整个文本文件读入字符数组

40

我想在C语言中将文本文件的内容读入char数组中,需要保留换行符。

如何实现?我在网上找到了一些C++的解决方案,但没有纯C的解决方案。

编辑:我现在有以下代码:

void *loadfile(char *file, int *size)
{
    FILE *fp;
    long lSize;
    char *buffer;

    fp = fopen ( file , "rb" );
    if( !fp ) perror(file),exit(1);

    fseek( fp , 0L , SEEK_END);
    lSize = ftell( fp );
    rewind( fp );

    /* allocate memory for entire content */
    buffer = calloc( 1, lSize+1 );
    if( !buffer ) fclose(fp),fputs("memory alloc fails",stderr),exit(1);

    /* copy the file into the buffer */
    if( 1!=fread( buffer , lSize, 1 , fp) )
      fclose(fp),free(buffer),fputs("entire read fails",stderr),exit(1);

    /* do your work here, buffer is a string contains the whole text */
    size = (int *)lSize;
    fclose(fp);
    return buffer;
}

我收到了一个警告:warning: assignment makes pointer from integer without a cast。这是在size = (int)lSize;这一行。如果我运行应用程序,它将导致段错误。

更新:上面的代码现在可以正常工作了。我找到了段错误,并发布了另一个问题。感谢您的帮助。


1
可能是 Easiest way to get file's contents in C 的重复。 - Ciro Santilli OurBigBook.com
3
使用fseek()函数获取文件大小,只能读取真实的磁盘文件。使用该函数意味着无法从管道(如标准输入)、命名管道、设备或网络流中读取数据。请参见上面评论中的链接Easiest way to get file's contents in C - anthony
请不要将答案编辑为问题。如果您想获得一个精练的版本,请发布您自己的答案。这个代码存在错误,例如 size = (int *)lSize; 将本地变量指针 size 设置为转换为指针的整数,但不会更新调用者传递指针的整数。(可能是你想写成 *size = lSize)。因此,这个有缺陷的答案应该被投票降低分数,但这是一个合理的问题。此外,你提到你发现(并修复)了一个段错误,但这是旧的代码还是修复后的代码?无论如何,即使它可以复制/粘贴,也不应该在问题中。 - Peter Cordes
5个回答

56
FILE *fp;
long lSize;
char *buffer;

fp = fopen ( "blah.txt" , "rb" );
if( !fp ) perror("blah.txt"),exit(1);

fseek( fp , 0L , SEEK_END);
lSize = ftell( fp );
rewind( fp );

/* allocate memory for entire content */
buffer = calloc( 1, lSize+1 );
if( !buffer ) fclose(fp),fputs("memory alloc fails",stderr),exit(1);

/* copy the file into the buffer */
if( 1!=fread( buffer , lSize, 1 , fp) )
  fclose(fp),free(buffer),fputs("entire read fails",stderr),exit(1);

/* do your work here, buffer is a string contains the whole text */

fclose(fp);
free(buffer);

22
你可以在处理数据之前关闭文件,而不是在之后。 - R.. GitHub STOP HELPING ICE
2
为什么要使用calloc而不是malloc? - Tanaki
3
通常我会使用calloc给C字符串分配内存作为一种冗余的安全机制,以防万一缓冲区中放入的C字符串由于某些原因没有以NUL结尾。尽管在大多数标准情况下这可能是一种不必要的预防措施。 - Ephemera
5
"fread" 处理原始数据,不会自动插入空终止符。使用 "calloc" 也会强制你的代码多迭代一次缓冲区,这是不必要的。 - diapir

13

这是一个完整程序的解决方案,可以回答问题并演示。相比其他答案,它更加明确,因此对于那些缺乏C经验的人来说更容易理解(个人看法)。

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

/*
 * 'slurp' reads the file identified by 'path' into a character buffer
 * pointed at by 'buf', optionally adding a terminating NUL if
 * 'add_nul' is true. On success, the size of the file is returned; on
 * failure, -1 is returned and ERRNO is set by the underlying system
 * or library call that failed.
 *
 * WARNING: 'slurp' malloc()s memory to '*buf' which must be freed by
 * the caller.
 */
long slurp(char const* path, char **buf, bool add_nul)
{
    FILE  *fp;
    size_t fsz;
    long   off_end;
    int    rc;

    /* Open the file */
    fp = fopen(path, "rb");
    if( NULL == fp ) {
        return -1L;
    }

    /* Seek to the end of the file */
    rc = fseek(fp, 0L, SEEK_END);
    if( 0 != rc ) {
        return -1L;
    }

    /* Byte offset to the end of the file (size) */
    if( 0 > (off_end = ftell(fp)) ) {
        return -1L;
    }
    fsz = (size_t)off_end;

    /* Allocate a buffer to hold the whole file */
    *buf = malloc( fsz+(int)add_nul );
    if( NULL == *buf ) {
        return -1L;
    }

    /* Rewind file pointer to start of file */
    rewind(fp);

    /* Slurp file into buffer */
    if( fsz != fread(*buf, 1, fsz, fp) ) {
        free(*buf);
        return -1L;
    }

    /* Close the file */
    if( EOF == fclose(fp) ) {
        free(*buf);
        return -1L;
    }

    if( add_nul ) {
        /* Make sure the buffer is NUL-terminated, just in case */
        buf[fsz] = '\0';
    }

    /* Return the file size */
    return (long)fsz;
}


/*
 * Usage message for demo (in main(), below)
 */
void usage(void) {
    fputs("USAGE: ./slurp <filename>\n", stderr);
    exit(1);
}


/*
 * Demonstrates a call to 'slurp'.
 */
int main(int argc, char *argv[]) {
    long  file_size;
    char *buf;

    /* Make sure there is at least one command-line argument */
    if( argc < 2 ) {
        usage();
    }

    /* Try the first command-line argument as a file name */
    file_size = slurp(argv[1], &buf, false);

    /* Bail if we get a negative file size back from slurp() */
    if( file_size < 0L ) {
        perror("File read failed");
        usage();
    }

    /* Write to stdout whatever slurp() read in */
    (void)fwrite(buf, 1, file_size, stdout);

    /* Remember to free() memory allocated by slurp() */
    free( buf );
    return 0;
}

1
在Windows上,至少需要以“rb”模式打开文件,否则fread将返回错误的数字。当add_nul为true时,我遇到了AccessViolation。我认为通过使用(*buf)[fsz] = '\0';来修复它。 - Ray Hulha
@RayHulha:说得好。我已经多年没有使用Windows了,往往会忘记它在二进制和文本模式之间的区别。你也是对的,原始代码中有一个多余的解引用符(多余的“*”)。 - Emmet
@Shark:是的,它可以工作。我不能声称它经过了广泛的测试,但在gcc -std=c99 -pedantic -Wall -Wextra下编译没有警告。我只是结合了@RayHulha的两个观察结果,但之前直接复制粘贴并编译就可以工作。它从来没有真正意义上成为一个库函数,只是一个演示。我将其更改为接受命令行上的文件名,而不总是从名为“foo.txt”的文件中读取,这可能更符合人们对完整程序的期望。 - Emmet
我的意思并不是有什么冒犯之处,但实际上它并没有。它有时候会引发未定义的行为和严重的泄漏。我使用这个泄漏了超过3.5GB的内存... 我贴出了我的解决方法。它在前几次尝试中确实运行良好,但正如你所说,它远远不能用于生产。但是嘿,它很好用,对于原型设计来说也足够了。应该也适用于作业 :) - Shark
它在哪里泄漏内存? - Ray Hulha
它在gcc下编译和运行时没有警告或错误。我不能保证在Windows下运行良好,但我已经从上面复制粘贴它,编译并在Linux上运行它。唯一可能泄漏内存的方式是如果调用者不遵守内存管理协议,即必须free()slurp() malloc()分配的空间。即使在这个小例子中,这样做的必要性也有明确的文档记录。 - Emmet

5

自从我使用slurp()并期望它能正常工作后,几天后我发现......它没有。

因此,对于那些渴望复制/粘贴“将文件内容获取到char *中”的解决方案的人,这里有一个可以使用的东西。

char* load_file(char const* path)
{
    char* buffer = 0;
    long length;
    FILE * f = fopen (path, "rb"); //was "rb"

    if (f)
    {
      fseek (f, 0, SEEK_END);
      length = ftell (f);
      fseek (f, 0, SEEK_SET);
      buffer = (char*)malloc ((length+1)*sizeof(char));
      if (buffer)
      {
        fread (buffer, sizeof(char), length, f);
      }
      fclose (f);
    }
    buffer[length] = '\0';
    // for (int i = 0; i < length; i++) {
    //     printf("buffer[%d] == %c\n", i, buffer[i]);
    // }
    //printf("buffer = %s\n", buffer);

    return buffer;
}

1
记住,孩子们,buffer必须由调用者释放。 - Shark
1
小bug修复: buffer[length+1] = '\0'; 应该改为: buffer[length] = '\0'; - Jos
在开始时应将length初始化为0,以防万一if (f)失败。 - vgru
1
如果f为空,在写入buffer时会发生访问冲突。同时,fread()的返回值也没有被检查错误。 - VLL
2
与其描述相反,人们绝不能将此复制/粘贴到严肃的程序中。它充斥着错误。 - Craig Barnes
1
我必须同意@CraigBarnes的观点 - 这不是生产就绪的代码,可能只适用于作业之类的小型项目。 - Shark

4
fgets()是一种C函数,可用于实现此操作。 编辑:您还可以考虑使用fread()。

2
在Windows上,您可能希望以二进制模式打开文件,以避免将换行符转换。 - Martin Beckett
不,它不会。它会读取到换行符或文件结尾。然而,读取的换行符会被保留。因此,您可以直接将读取的字符附加到char数组中,换行符将以与文件相同的方式出现。 - Shamim Hafiz - MSFT
1
使用fgets来完成这个任务没有任何意义。它会比单独使用fread要复杂得多,并且容易出错。例如,请考虑处理嵌入的NUL字节所需的额外工作。 - R.. GitHub STOP HELPING ICE
@MartinBeckett 天哪,谢谢您!我字符串末尾出现了一些随机字符,我已经头痛了两个小时了。我还不确定是否需要在末尾添加 content[size] = '\0'; 或者是我做错了什么,或者这只适用于Windows系统。 - user170934

0
我使用了以下代码将XML文件读入字符缓冲区,但是我不得不在文件末尾添加\0。
FILE *fptr;
char *msg;
long length;
size_t read_s = 0;  
fptr = fopen("example_test.xml", "rb");
fseek(fptr, 0L, SEEK_END);
length = ftell(fptr);
rewind(fptr);
msg = (char*)malloc((length+1));
read_s = fread(msg, 1, length, fptr);
*(mip_msg+ read_s) = 0;
if (fptr) fclose(fptr);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接