使用 fclose 的奇怪行为

Strange behavior using fclose

提问人:Dalton Cézane 提问时间:5/11/2023 最后编辑:OkaDalton Cézane 更新时间:5/12/2023 访问量:96

问:

测试 fclose 函数时,未到达该行。如果删除该行,则显示该消息。printf("File \"file2.bin\" inexistent!");fclose(p_file2)

#include <stdio.h>

int main()
{
    printf("Hello files");
    FILE *p_file1, *p_file2;
    p_file1 = fopen("file1.bin", "w+b");
    p_file2 = fopen("file2.bin", "r+b");
    if (p_file2 == NULL)
        printf("File \"file2.bin\" inexistent!");
    
    fclose(p_file1);
    fclose(p_file2);

    return 0;
}

为什么会这样?

C 文件 fclose

评论

3赞 William Pursell 5/11/2023
做什么?为什么你认为这条线没有到达?你唯一的证据是字符串“File \”file2.bin\“ inexistent!”没有出现在输出中,但你得出这意味着代码行未执行的结论是不正确的。fclose(NULL)printf(...)
0赞 Adrian Mole 5/11/2023
什么编译器?什么平台?
1赞 Andreas Wenzel 5/11/2023
如果要确定是否到达该行,可以在调试器中逐行运行程序。如果你这样做,你会注意到这条线已经到达。请参阅@WilliamPursell的答案,以解释您如何可能不查看输出,尽管已到达该行。printf("File \"file2.bin\" inexistent!");
1赞 William Pursell 5/11/2023
另请注意,返回 NULL from 并不一定意味着文件不存在。返回 NULL 的原因有很多,如果盲目假设原因,可能会使用户感到困惑。让系统告诉你失败的原因。(例如:,它也会将错误消息写入正确的流。fopenfopenfopenperror(path)
0赞 Andreas Wenzel 5/11/2023
@WilliamPursell:与POSIX相比,ISO C不要求在失败时设置。因此,您推荐的内容并不能保证有效。但是,我相信它会在最常见的平台上工作。fopenerrno

答:

4赞 William Pursell 5/11/2023 #1

这里有 2 个主要问题。首先是导致未定义的行为,所以对另一点的推测真的没有意义,但这显然是混乱的根源。 并不总是将任何数据写入输出流。相反,数据被缓冲,写入可能会推迟到以后对程序的调用或直到程序退出(或调用 or 或其他条件)。由于您的程序被调用,因此发生了不好的事情,并且本应延迟的写入从未发生,因此您看不到该消息。fclose(NULL)printfprintffwritefflushfclose(NULL)

尝试在 之后立即添加一个调用。fflushprintf

评论

2赞 G. Sliepen 5/11/2023
在格式字符串的末尾添加换行符 ('\n') 也会有所帮助,通常标准输出是行缓冲的。
3赞 John Bollinger 5/11/2023
fsync()不是适合这项工作的工具,而且在这种情况下它不容易使用,因为它需要文件描述符,而不是流。也许你的意思是?fflush()
0赞 Andreas Wenzel 5/11/2023
您可能想澄清有必要编写 ,否则,OP 可能会尝试刷新其他流之一。fflush( stdout );
1赞 Konrad Rudolph 5/11/2023
@AndreasWenzel 是的,我的评论是 wong,实际上我什至找不到那是 UB。威廉,你能引用一个参考资料说这是UB吗?fclose(NULL)
1赞 Andreas Wenzel 5/11/2023
@KonradRudolph:我认为根据 ISO C11 标准 §7.1.4 ¶1 的一般规则,该行为是未定义的。
1赞 Jabberwocky 5/11/2023 #2

仅调用 实际返回的有效 FILE 指针。fclosefopen

基本上你想要这个:

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

int main()
{
  printf("Hello files");
  FILE* p_file1, * p_file2;
  p_file1 = fopen("file1.bin", "w+b");
  if (p_file1 == NULL)
  {
    printf("Can't open file.\n");  // add \n
    exit(1);
  }

  p_file2 = fopen("file2.bin", "r+b");
  if (p_file2 == NULL)
  {
    fclose(p_file1); // this can be ommited because files are closed automatically upon exit
    printf("Can't open file.\n");   // add \n
    exit(1);
  }
  
  // here we know for sure that both p_file1 and p_file1 are valid
  fclose(p_file1);
  fclose(p_file2);

  return 0;
3赞 Harith 5/11/2023 #3
  1. 传递指针以调用未定义的行为。NULLfclose()

当程序在失败时退出时,我们可以有这样的函数:fopen()

static void open_sesame (const char *restrict stream, const char *restrict mode) 
{
    if (!fopen (stream, mode)) {
        perror ("fopen():");
        exit (EXIT_FAILURE);
    }
}

现在,您的代码简化为:

FILE *fp1 = open_sesame ("file1.bin", "w+b");
FILE *fp2 = open_sesame ("file2.bin", "r+b");
...
fclose (fp1);
fclose (fp2);

  1. 缓冲的数据在调用时或程序终止时被刷新。如果是行缓冲的(通常在流连接到终端/控制台时出现这种情况),则当遇到换行符时,流也将被刷新。但是,由于程序调用了未定义的行为,因此无法对程序执行的继续做出假设。printf()fflush (stdout)stdout

在调用 或调用 中添加换行符。printf()fflush (stdout);


脚注:

该标准未定义行为,因此此规则适用:

除非明确说明,否则以下每一项声明均适用 否则在下面的详细说明中: 如果参数 函数具有无效值(例如,域外的值 函数,或程序地址空间外的指针, 或 null 指针,或指向不可修改存储的指针,当 相应的参数不是 const 限定的)或类型(在 promotion) 不是由具有可变数 参数,则行为未定义。— 7.1.4p1 C18 标准。

评论

0赞 Konrad Rudolph 5/11/2023
你有成为UB的参考吗?我手头没有当前的 C 标准文档,但据我所知,旧版本并没有这么说(除非我忽略了所有库函数都需要有效指针的措辞)。fclose(NULL)
0赞 Harith 5/12/2023
@KonradRudolph 我添加了相关规则。