【问题标题】:Reading the whole text file into a char array in C将整个文本文件读入C中的char数组
【发布时间】:2011-04-14 09:49:06
【问题描述】:

我想将文本文件的内容读入 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;
}

我收到一个警告:警告:赋值使指针从整数而不进行强制转换。这是在线size = (int)lSize;。如果我运行该应用程序,它会出现段错误。

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

【问题讨论】:

  • 使用 fseek() 来获取文件的大小,限制你只读取真实的磁盘文件。使用它意味着您不能从管道(如标准输入)、命名管道、设备或网络流中读取数据。查看上面评论中的链接Easiest way to get file's contents in C
  • 请不要将答案编辑成问题。如果您想要一个完善的版本,请发布您自己的答案。这有像size = (int *)lSize; 这样的错误,它将指针局部变量size 设置为一个整数转换为指针,但没有做任何事情来更新调用者传递一个指针的int。 (可能你的意思是*size = lSize)。所以这个错误的答案应该被否决,但这是一个合理的问题。另外,您提到您发现(并修复了?)一个段错误,但这是旧代码还是固定代码?无论如何,即使可以复制/粘贴也不应该出现在 Q 中

标签: c text file-io


【解决方案1】:

我使用以下代码将 xml 文件读入 char 缓冲区,我必须在文件末尾添加 \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);

【讨论】:

    【解决方案2】:

    由于我使用 slurp() 并希望它能够工作,几天后我发现......它没有。

    因此,对于渴望复制/粘贴解决方案以“将 FILE 的内容转换为 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;
    }
    

    【讨论】:

    • 记住孩子们,buffer 必须被调用者释放。
    • 编辑必须至少有 6 个字符,因此无法修复。小错误修复:buffer[length+1] = '\0'; 应该是:buffer[length] = '\0';
    • 如果f 为NULL,写入buffer 时会发生访问冲突。也不会检查 fread() 的返回值是否有错误。
    • 与描述相反,人们绝对应该将其复制/粘贴到一个严肃的程序中。它绝对充满了错误。
    • 我必须同意@CraigBarnes - 这不是生产就绪代码,应该用于比家庭作业更严肃的事情。
    【解决方案3】:

    以完整程序的形式回答问题并进行演示的解决方案。它比其他答案更明确一些,因此对于那些在 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;
    }
    

    【讨论】:

    • 至少在 Windows 上,您需要以“rb”模式打开文件,否则 fread 将返回错误的数字。当 add_nul 为真时,我得到了 AccessViolation。我想我用这个修复了它:(*buf)[fsz] = '\0';
    • @RayHulha:公平点。我已经很多年没有使用过 Windows 并且倾向于忘记区分二进制和文本模式。您在第二点上也是正确的,原始文件中有一个无关的取消引用(多余的“*”)。
    • @Shark:是的,它有效。我不能声称它已经过广泛测试,但它在gcc -std=c99 -pedantic -Wall -Wextra 下编译时没有警告。我刚刚合并了@RayHulha 的两个观察结果,但是之前直接复制粘贴和编译是可行的。它从来没有真正打算成为一个库函数,只是一个演示。我对其进行了更改,使其在命令行上接受文件名,而不是总是从名为 foo.txt 的文件中读取,这可能更像人们对完整程序的期望。
    • 我没有冒犯的意思,但实际上并没有。它有时会调用未定义的行为并大量泄漏。我使用这个泄漏了超过 3.5GB 的内存......我粘贴了我的解决方法。在最初的几次尝试中,它确实确​​实工作得很好,但正如你所说,它还远未准备好生产。但是,嘿,这很好,并且是原型制作的诀窍。也应该为家庭作业工作:)
    • 内存泄漏在哪里?
    【解决方案4】:

    fgets() 是一个可用于完成此任务的 C 函数。

    编辑:您也可以考虑使用 fread()。

    【讨论】:

    • 在 Windows 上您可能希望以二进制模式打开,这样它就不会翻译 cr
    • 不,它没有。它读取到换行符或文件末尾。但是,会保留换行读取。因此,您可以将读取的字符直接附加到 char 数组中,并且换行符将以与文件相同的方式出现。
    • 为此目的使用fgets 毫无意义。这将比单个fread 复杂得多,而且更容易出错。考虑一下处理嵌入的 NUL 字节所必须做的额外工作,例如..
    • @MartinBeckett OMFSM 谢谢!字符串末尾的随机字符,2 小时将我的头撞在墙上。还必须添加 content[size] = '\0';最后,不确定这是 Windows 特定的还是我做错了什么。
    【解决方案5】:
    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);
    

    【讨论】:

    • 您可以在处理数据之前关闭文件,而不是之后。
    • calloc优于malloc的任何特殊原因?
    • @Tanaki 我通常调用 C 字符串作为冗余安全机制,以防万一放入缓冲区的 C 字符串由于某种原因不是 NUL 终止的。不过,在大多数标准情况下,这可能是不必要的预防措施。
    • @ephemera: fread 处理原始数据,不会打扰插入空终止符。使用calloc 还会强制您的代码在缓冲区上迭代一次而不是必要的次数。
    猜你喜欢
    • 2011-05-21
    • 2012-10-20
    • 1970-01-01
    • 1970-01-01
    • 2015-07-11
    • 1970-01-01
    • 1970-01-01
    • 2020-01-20
    • 2010-09-29
    相关资源
    最近更新 更多