【问题标题】:why does fclose not set file pointer to NULL?为什么 fclose 不将文件指针设置为 NULL?
【发布时间】:2011-09-01 21:58:27
【问题描述】:

我正在为 FILE * 编写一个 RAII 包装器。我注意到当 FILE * 在析构函数中关闭后被删除时,它会导致未定义的行为(例如错误或其他地方的错误)。我假设 fclose 会将 FILE * 设置为 NULL,但事实并非如此。

class smartFP {
  smartFP (const std::string& name) 
  : fp (fopen(name.c_str(), "r")
  { }

  ~smartFP()
  { 
     if (fp) {
        fclose(fp); 
        // delete(fp); <- This is causing crash
        fp = NULL; <- Is this OK?
     }
  }

private:
   FILE *fp;
};
  • 为什么 fclose 不将 FILE * 设置为 NULL?
  • 第二个问题是fopen是在堆还是栈中为fp分配内存?我认为它在堆上,因此想在 fclose 之后进行删除,以便释放 fp 堆上的 4 或 8 个字节。但看起来这不是必需的。

【问题讨论】:

  • 您为什么要为FILE* 编写 RAII 包装器?如果您已经在使用 C++,为什么不直接使用 fstream
  • 升级旧软件是一个常见的原因。我已经做到了。与将其全部替换为流相比,文件* 周围的 RAII 更改要小得多。
  • @Adam - MD 是对的。用 fstream 替换现有文件 * 需要更多更改,特别是如果某些用于读取二进制文件的库需要传递 FILE * 时。

标签: c++ pointers fclose


【解决方案1】:

当然delete fp 会导致崩溃。它没有分配给new。仅在您使用new 获得的东西或文档告诉您使用它的其他东西上调用deletefopen 的文档从未告诉您使用 delete。文件的所有清理工作由fclose 执行;调用文件相关资源后,您无需执行任何其他操作。

设置fp = NULL 没问题。这可能是可取的,以便这个“智能文件指针”的未来消费者可以检查指针是否仍然有效。 (不过,reset 方法比析构函数更有用;在析构函数运行后,指针类不会有任何未来的消费者,因为该对象不再存在。)但是 fclose 可以t 自己这样做是因为fclose 不会通过引用接收其参数,即使这样做,它也无法使文件指针的所有 可能的副本无效。回想一下 freedelete 也不要将它们的参数设置为 NULL

【讨论】:

  • 我想到了你提到的关于在析构函数中将 fp 设置为 NULL 的观点。我在 smartFP 类中有一个返回 FILE * 的 API: FILE * get() { return fp; } 。这样做的原因是在使用 FILE * 的现有代码中执行 smartFPObj.get()。我不希望有人保留 fp 的副本并在 smartFPObj 超出范围后尝试使用它。
  • 您的get 方法很好,但您在析构函数中的操作对您毫无帮助。如果有人调用get 并存储了结果的副本,那么他们就有了结果的副本。您可以将 fp 变量更改为您想要的任何值,但不会影响副本中的值。考虑以下代码:FILE* fp = fopen(...); FILE* b = fp; fclose(fp); fp = NULL;fp 设置为NULL 不会改变bfp 是否是类的成员,或者 b 的分配是否在调用某个 get 方法之后进行,这没有区别。
  • @srikrish 仅仅因为您将类中的指针成员设置为 NULL 并不意味着他们保存的副本将设置为 NULL。 Rob,fclose 是一个 C 函数,因此它不可能通过引用接收 FILE*(当然,它可能需要 FILE**)。
  • @Rob - 感谢您的澄清。所以当我做 fp = fopen(..) 时,fp 是分配在调用函数的堆还是堆栈上。同样,当您坐在“所有由 fclose 完成的文件的清理”时,这是否意味着为私有成员 fp(在堆栈或堆中)分配的空间被取消分配或稍后发生。
  • @Rob - 也为 FILE * b 分配空间,它是 fp 的副本,在堆栈或堆中分配?是否有 FILE * 的复制赋值运算符可以启用此复制。
【解决方案2】:

不,您不应该尝试delete 文件 *。它是一个 C 库数据结构,并不代表从 C++ new 返回的指针。

【讨论】:

    【解决方案3】:

    fp 不是由 C 运行时分配的,您不必释放它。 fclose 不会将其设置为 NULL,因为它不能将其设置为 null(它是指向 FILE 结构的指针,而不是指向 FILE *)。

    如何分配 FP 不是您关心的问题,它不是您与 API 合同的一部分,所以不用担心。

    附录:

    fopen 正在返回一个指向 FILE 结构的指针。它在何处以及如何获取内存并不重要。它很可能指向内存中的静态结构。但是,基本上,你不需要为那段记忆负责,所以你不应该搞砸它。

    现在,实际的 fp 指针,您可以分配它:

    FILE **fp = malloc(sizeof(FILE *));
    *fp = fopen("file.txt", "r");
    ...
    fclose(*fp);
    free(fp);
    

    但是,很明显,大多数人不这样做,他们只是使用本地堆栈变量来管理它。取决于用例。

    【讨论】:

    • 正如我所解释的,删除 fp 的原因是释放 fopen 分配给私有成员 fp 的任何空间。因为,我认为 fclose 只释放内存中分配给文件的空间,所以我想删除 fp 并释放空间。仍然不清楚,为什么不需要这样做。是因为 fclose 完成了这项工作。
    【解决方案4】:

    fopenfclose 使用FILE * 元素进行必要的动态内存管理,因此您不必对它们调用delete。但是,如果您愿意,您可以在其中放置一个 NULL 值,以表明该文件未分配。

    【讨论】:

      【解决方案5】:

      1) fclose 不能 将 FILE* 设置为 NULL,因为它的参数是 FILE*,所以它通过值接收 FILE*。因为它是一个副本,所以它不能改变你的指针,除非参数被更改为 FILE*&。
      2) 在 C++ 中,你几乎从不调用 delete,除非你调用了 new。如果你在做 RAII,你应该只将 new 分配给智能指针,永远不要调用 delete

      【讨论】:

        猜你喜欢
        • 2010-10-16
        • 1970-01-01
        • 2011-04-19
        • 2013-05-10
        • 1970-01-01
        • 1970-01-01
        • 2011-08-22
        • 1970-01-01
        相关资源
        最近更新 更多