【问题标题】:Trying to read an unknown string length from a file using fgetc()尝试使用 fgetc() 从文件中读取未知字符串长度
【发布时间】:2020-04-10 03:43:54
【问题描述】:

是的,看到了许多与此类似的问题,但想尝试以我的方式解决它。运行后获取大量文本块(编译正常)。

我试图从文件中获取未知大小的字符串。考虑分配大小为 2 的 pts(1 个字符和空终止符),然后使用 malloc 为每个超过数组大小的字符增加 char 数组的大小。

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

int main()
{
    char *pts = NULL;
    int temp = 0;

    pts = malloc(2 * sizeof(char));
    FILE *fp = fopen("txtfile", "r");
    while (fgetc(fp) != EOF) {
        if (strlen(pts) == temp) {
            pts = realloc(pts, sizeof(char));
        }
        pts[temp] = fgetc(fp);
        temp++;
    }

    printf("the full string is a s follows : %s\n", pts);
    free(pts);
    fclose(fp);

    return 0;
}

【问题讨论】:

  • 有趣的问题,但究竟是什么问题?如果你想得到一个未知大小的字符串,结果是一大块文本,那不成功吗?听起来你把整个文件都读给我听了。
  • 此时 - strlen(pts) 你不知道 pts 里面是什么,你在它上面调用 strlen() 从而导致 UB。也许calloc() 会是更好的选择?
  • 最坏情况,字符串与文件大小相同;你能分配这么多内存吗?
  • pts = realloc(pts, sizeof(char)) 不会扩展缓冲区,但总是分配 1 个字节。您必须指定 total 长度
  • 每次执行时会发生什么:while (fgetc(fp) != EOF) {?从文件中读取了一些内容,但它去了哪里?

标签: c string file string-length fgetc


【解决方案1】:

你可能想要这样的东西:

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

#define CHUNK_SIZE 1000               // initial buffer size

int main()
{
  int ch;                             // you need int, not char for EOF
  int size = CHUNK_SIZE;

  char *pts = malloc(CHUNK_SIZE);
  FILE* fp = fopen("txtfile", "r");

  int i = 0;
  while ((ch = fgetc(fp)) != EOF)     // read one char until EOF 
  {
    pts[i++] = ch;                    // add char into buffer

    if (i == size + CHUNK_SIZE)       // if buffer full ...
    {
      size += CHUNK_SIZE;             // increase buffer size
      pts = realloc(pts, size);       // reallocate new size
    }
  }

  pts[i] = 0;                        // add NUL terminator

  printf("the full string is a s follows : %s\n", pts);
  free(pts);
  fclose(fp);

  return 0;
}

免责声明:

  1. 这是未经测试的代码,它可能不起作用,但它显示了这个想法
  2. 为了简洁起见,绝对没有错误检查,您应该添加这个。
  3. 还有其他改进的空间,可能会做得更优雅

【讨论】:

  • 为什么不直接计算文件长度,分配内存,然后写入呢? realloc 似乎增加了不必要的复杂性。
  • @FiddlingBits 你怎么知道标准输入的大小?
  • 这是一个很好的观点,但 OP 是专门询问一个文件。
  • 一般的解决方案更灵活,但你是对的,你也可以检查文件大小(例如stat())并分配一次。
  • @FiddlingBits 重点还在于展示如何正确执行 realloc 操作,但是是的,如果只是将文件完全读入内存,然后获取文件长度,分配正确的内存大小并且使用单个 fread 读取文件肯定更简单、更高效。
【解决方案2】:

暂时搁置如果的问题,你应该这样做:

你已经很接近这个解决方案了,但是有一些错误

while (fgetc(fp) != EOF) {

这一行将从文件中读取一个字符,然后在将其与 EOF 进行比较后将其丢弃。您需要保存该字节以添加到缓冲区。像while ((tmp=fgetc(fp)) != EOF) 这样的语法应该可以工作。

pts = realloc(pts, sizeof(char));

勾选the documentation for realloc,你需要在第二个参数中传入新的尺寸。

pts = malloc(2 * sizeof(char));

您需要在获取此内存后将其归零。您可能还希望将 realloc 提供给您的任何内存归零,否则您可能会丢失字符串末尾的空值,strlen 将不正确。


但正如我之前提到的,当您已经对缓冲区的大小有了一个清晰的认识时,在这样的循环中使用 realloc 通常是非惯用的 C 设计。提前获取文件的大小,并为缓冲区中的所有数据分配足够的空间。如果超过缓冲区的大小,您仍然可以重新分配,但是使用内存块而不是一次一个字节。

【讨论】:

  • 你不应该检查'\0'而不是EOF吗?
  • null 是 C 中内存缓冲区的字符串结尾标记。文件本身不需要以 null 结尾,也不需要在其中包含任何 null。添加阅读:en.cppreference.com/w/c/io/fgetc
  • “您需要在获取此内存后将其归零。” - 这适用于 OP 的错误实现,但如果您在末尾正确添加 0 终止符,则是不必要的。
  • @Segfault Heya,首先感谢您的输入:D。我根本没有得到第三件事(归零的事情)。你能详细说明一下这个话题吗?是的,我知道获取文件本身的大小会更聪明,但我想推进这项技术,这样它就可以“转移”到其他信息源。它可能是一个文件或一个控制台命令或 w.e 所以我试图让一个通用代码与我得到的 w.e 输入源一起工作。
  • 关于归零的要点是确保您的缓冲区(字符串)正确地以空值终止。 Sean Bright 说,如果您正确添加空终止符,这不是问题,他是正确的。问题是 malloc 和 realloc 正在向您返回未初始化的内存,因此其中会有一些垃圾数据。 Malloc 在将内存提供给您之前不会将内存初始化为零。当您开始将数据写入您已经 malloc 的缓冲区时,除非您自己这样做,否则它不会以空值终止。在分配它们之后将所有字节设置为零是一种简单的方法。
【解决方案3】:

可能最有效的方法是(如comment by Fiddling Bits 中所述)是一次性读取整个文件(在第一次获取文件大小之后):

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>

int main()
{
    size_t nchars = 0; // Declare here and set to zero...
    // ... so we can optionally try using the "stat" function, if the O/S supports it...
    struct stat st;
    if (stat("txtfile", &st) == 0) nchars = st.st_size;

    FILE* fp = fopen("txtfile", "rb"); // Make sure we open in BINARY mode!
    if (nchars == 0) // This code will be used if the "stat" function is unavailable or failed ...
    {
        fseek(fp, 0, SEEK_END); // Go to end of file (NOTE: SEEK_END may not be implemented - but PROBABLY is!)
    //  while (fgetc(fp) != EOF) {} // If your system doesn't implement SEEK_END, you can do this instead:
        nchars = (size_t)(ftell(fp)); // Add one for NUL terminator
    }
    char* pts = calloc(nchars + 1, sizeof(char));

    if (pts != NULL)
    {
        fseek(fp, 0, SEEK_SET); // Return to start of file...
        fread(pts, sizeof(char), nchars, fp); // ... and read one great big chunk!
        printf("the full string is a s follows : %s\n", pts);
        free(pts);
    }
    else
    {
        printf("the file is too big for me to handle (%zu bytes)!", nchars);
    }
    fclose(fp);
    return 0;
}

关于SEEK_END的使用问题,见this cppreference页面,其中声明:

  • 允许库实现无意义地支持 SEEK_END(因此,使用它的代码没有真正的标准可移植性)。

关于您是否可以使用stat 功能,请参阅this Wikipedia 页面。 (但它现在在 Windows 上的 MSVC 中可用!)

【讨论】:

  • 只需使用stat()
  • @Sean - 但这仅在 POSIX、IIRC 中可用。 (或者只是 Linux/Unix?)
  • 这是 POSIX。我假设 OP 的操作系统支持它。
  • @SeanBright 我添加了一个stat() 选项(和一个错误检查)。感谢您的提示!
猜你喜欢
  • 2011-05-14
  • 2020-05-03
  • 2013-01-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-04
相关资源
最近更新 更多