【问题标题】:Error when reading a file with "fgetc" (Overflow)使用“fgetc”读取文件时出错(溢出)
【发布时间】:2024-04-14 22:15:01
【问题描述】:

我正在使用此代码读取文件:

char* fs_read_line(FILE* file)
{
   if (file == NULL) {
       return "CFILEIO: Error while reading the file: Invalid File";
   }

   long threshold = ftell(file);
   fseek(file, 0, SEEK_END);
   uint8_t* buffer = calloc(ftell(file)-threshold, sizeof(uint8_t));

   if(buffer == NULL)
      return;

   int8_t _;
   fseek(file, threshold, SEEK_SET);

   uint32_t ct = 0;
   while ((_ = (char)(fgetc(file))) != '\n' 
        && _ != '\0' &&  _ != '\r' && _ != EOF) {
       buffer[ct++] = _;
   }

   buffer = realloc(buffer, sizeof *buffer * (ct + 1)); 
   buffer[ct] = '\0';
   return buffer;
}

如果文件太大,我会得到(堆)溢出错误,可能是因为我最初用它包含的字符总数分配文件。

我尝试这样做的另一种方法是在每次迭代后通过 realloc 缓冲区,但这有点不是我想要的方法。

有没有办法根据当前迭代动态改变数组的大小,而不总是 uisng realloc ?或者有没有办法通过使用ftellfseek 来确定当前行的长度?

【问题讨论】:

  • 您显示的代码无法在 C++ 中编译。请不要添加无关的标签。
  • sizeof(uint8_t*) 为您提供指针的大小,而不是单个字节的大小。在uint8_t 存在的系统上,这几乎完全可以肯定只有 1。所以你分配文件大小的 4 或 8 倍,然后你不检查分配是否成功。
  • 相比memsetbuffer[ct++] = _清晰很多。
  • 您的代码也存在一些问题,包括它可能返回一个无法调用 free 的字符串字面量指针,并且您没有任何类型的错误检查callocrealloc 来电。无论如何都不需要realloc 调用,这很好,因为你用错了。为什么要使用memset 来复制单个字节?为什么不使用例如buffer[ct++] = _?
  • 哦,记住 fgetc 返回的是 int 而不是 char

标签: c file io c11


【解决方案1】:

代码不返回指向字符串的指针。

返回的buffer 中没有空字符,所以调用代码缺乏知道分配内存长度的能力。这肯定会导致调用代码出错。

重新分配时,加1。

// buffer = realloc(buffer, ct * sizeof(uint8_t*));
//                                          v--- no star
buffer = realloc(buffer, ct * sizeof(uint8_t ) + 1);
buffer[ct] = '\0';

// or better
size_t ct = 0;
...
buffer = realloc(buffer, sizeof *buffer * (ct + 1));
buffer[ct] = '\0';

有什么方法可以根据当前迭代动态更改 array 分配的内存 的大小,而不总是使用realloc

数组大小不能改变。动态改变分配内存的大小需要realloc()。注意:需要的内存量可以在内存分配调用之前确定。

或者有没有办法通过使用ftellfseek 来确定当前行的长度?

像这段代码一样,您已经找到了当前行长度的上限。 ftellfseek 不定位行尾。

代码可以使用fscanf(file, "%*[^\n]");“搜索”到行尾,或者使用后续fgetc(file)“搜索”1 行之后。

【讨论】:

  • 顺便问一下,如果不先释放分配的内存就缩小分配的内存,会不会造成内存泄漏?
  • @Lupor 否。但是您可能在调用代码中存在泄漏-它是否释放了此函数返回的指针?注意:一旦内存被释放,代码就不能使用指针值。代码无法根据该指针重新分配。
  • 是的,我总是释放函数返回的指针。谢谢你!但最后一个问题:为什么我在调用 realloc 时必须使用 sizeof(uint8_t*) 而不是 sizeof(uint8_t) ?为什么我在执行sizeof(uint8_t) 时会收到垃圾字节?
  • @Lupor 代码不需要使用uint8_t*。不清楚你是如何确定你得到“垃圾字节”的。 IAC,建议分配引用变量的大小,而不是类型并附加一个空字符。 buffer = realloc(buffer, sizeof *buffer * (ct + 1)); buffer[ct] = '\0';
【解决方案2】:

如果您的文件无法放入内存,它就无法放入内存。您提前分配了内存缓冲区,但您犯了两个错误,这可能导致您分配的内存超出您的需要。

  1. 您从文件中的任意位置开始,但分配内存时就像从文件开头开始一样。分配 ftell(file) - threshold 字节。
  2. 您分配的内存过多。 sizeof(uint8_t *) 应改为 sizeof(uint8_t)。您分配的内存是应有的 4 或 8 倍。

除此之外,在完成写入之后重新分配缓冲区的意义何在?内存溢出已经发生了。您应该在写入之前分配(在 while 循环内)。不过,我根本看不出重新分配的意义,因为您一开始就分配了足够多的内存。

【讨论】:

  • 由于某种原因,如果我删除realloc 处的sizeof(uint8_t*),我会得到垃圾字节^^ 但我在初始分配时删除了sizeof(uint8_t*) 并添加了ftell(file) - threshold,谢谢!
  • 你为什么要重新分配?
  • buffer ptr 的大小缩小到实际读取字节数的大小,因为我最初分配buffer 的字节数大于实际线^^
【解决方案3】:

以下代码:

  1. 干净编译
  2. 执行所需的操作
  3. 正确处理错误情况
  4. 正确声明变量类型
  5. 正确返回char* 而不是uint8_t*
  6. 留下一个问题:为什么返回所需缓冲区长度的 2 倍
  7. 传入参数为NULL时显示的错误信息不正确。建议更改以指示传入的文件指针为 NULL
  8. OP 发布的代码无法检查每次调用 fseek() 的返回值,也无法检查每次调用 ftell() 的返回值,它应该这样做以确保操作成功。我没有在我的答案中添加错误检查,以免使代码混乱,但是应该执行它。

现在,代码:

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


char* fs_read_line(FILE* file);


char* fs_read_line(FILE* file)
{
   if ( !file )
   {
       return "CFILEIO: Error while reading the file: Invalid File";
   }

   // implied else, valid parameter

   long threshold = ftell(file);
   fseek(file, 0, SEEK_END);

   char* buffer = calloc( (size_t)(ftell(file) - threshold) *2 +1, sizeof(char));
   if(buffer == NULL)
      return NULL;

   // implied else, calloc successful

   int ch;
   fseek(file, threshold, SEEK_SET);

   size_t ct;
   while ( (ch = fgetc(file)) != '\n'
        &&  ch != '\0'
        &&  ch != '\r'
        &&  ch != EOF)
   {
       buffer[ct++] = (char)ch;
   }

   return buffer;
} // end function: fs_read_line

【讨论】: