检查已使用fopen打开的文件是否已关闭。

6
gcc (GCC) 4.7.2
c89

有没有办法检查文件是否已经关闭?

我使用fopen()打开了一个文件,并使用fclose(fd)关闭了它。

在程序运行期间,此文件会被打开和关闭。然而,用户可以通过执行ctrl-c来终止程序。

当我进入清理程序时,我不知道文件是处于打开状态还是关闭状态。因此,如果我尝试两次使用fclose(fd),它将导致堆栈转储。

我一直在思考一些想法:

  1. 为文件添加打开或关闭状态,然后检查该状态,这意味着为一个简单的工作需要更多的工作。
  2. 什么也不做,因为操作系统会在程序结束时自动清理。
  3. 是否有访问函数,但那只能检查模式。

非常感谢您的帮助!


添加一个状态(第一个选项)会添加一个竞态条件,除非您在状态与文件同步时屏蔽Ctrl+C信号。 - nullptr
4
为什么在关闭文件句柄后不能将 fd(奇怪的名称)设置为 NULL - VoidPointer
这更与libc有关(通常是Glibc,但也可能是musl-libc等...),而不是编译器。 - Basile Starynkevitch
将一个名为OPENCLOSE的状态变量定义为#define。每当您在代码中调用fopen时,将状态设置为Current_State = OPEN;,并且当您在代码中调用fclose时,将状态设置为Current_State = CLOSE;,并在需要时使用该current_state变量。 - user2045557
5个回答

7
据我所知,glibc没有提供验证FILE*变量的方法。
保持某种状态或仅将fd设置为NULL是可行的,但您必须小心,在关闭文件后但在重置fd之前不要进入清理程序。由于Ctrl+C会向程序发送一个信号(SIGINT),因此您可以在关闭文件并重置fd时阻止该信号。因此,您主程序中的fclose应如下所示:
// 1. Block SIGINT
sigset_t old_mask, to_block;
sigemptyset(&to_block);
sigaddset(&to_block, SIGINT);
sigprocmask(SIG_BLOCK, &to_block, &old_mask);

// 2. Close the file and reset fd
fclose(fd);
fd = NULL;

// 3. Restore signal handling
sigprocmask(SIG_SETMASK, &old_mask, NULL);

在你的清理程序中,你只需检查fd

if (fd != NULL) fclose(fd);

如果你的程序是多线程的,应该使用pthread_sigmask
在你的情况下,在清理例程中简单调用fcloseall()会更容易。
至于你问题中提到的第二个选项,即什么也不做-好吧,操作系统将为你的程序清理一切。唯一的缺点是,如果有打开的写流,可能有些数据没有写入磁盘。但是在Ctrl+C的情况下,也许这就可以了。

AFAIK是什么意思?顺便问一下。 - user2045557
8
据我所知,AFAIK是“ As far as I know ”的缩写 :) - nullptr

4

我认为你的思维有点混乱。

文件处理器是与 fopenfclosef... 函数一起使用的,它们是 进程作用域。也就是说,它们在应用程序内有效(通常情况下)。

打开文件时可能会发生两件事:成功或失败。如果成功,则您可以独占地使用文件或与其他进程共享。如果失败,则可能有另一个进程已经在使用该文件。请搜索 _fsopen

当进程正常结束或被中断(如 Ctrl+C),操作系统会释放与进程相关联的资源,早晚都会释放。这适用于文件处理器、内存等。

关于你的问题,无论你的应用程序是正常结束还是通过 Ctrl+C 中断,未关闭的文件处理器都将被操作系统释放。

每次应用程序启动时,需要打开所需的文件处理器。操作系统不会为您保持它们处于打开状态。


2
我在不同的情况下遇到了类似的问题(没有竞争条件)。因此,这个解决方案涵盖了标题中提出的问题和ant提出的问题。
当调用fclose(fd)时,内部fd->_fileno低级I/O文件描述符设置为-1(或其他无效的Unix文件描述符)。因此,可以使用fileno(fd)>=0检查fd是否仍然是有效流(我的原始问题)。
当按下CTRL-C时,进程接收到一个信号,称为SIGINT,并执行相应的信号处理程序(例如预定义的函数)。永远不要尝试在信号处理程序中执行涉及程序状态的任何操作(除了原子可设置变量,例如标志),因为信号可能在任何情况下到达,所以即使在glibc中执行fclose(fd)(因此fd->_fileno在处理程序执行期间未定义)。因此,在这里的规范方式是设置一个触发关闭的标志并返回。然后,主应用程序需要足够频繁地检查此标志,并且如果已设置,则清理并退出。
在按下CTRL-C后,此程序完成,而不导致系统清理任何fd。
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>

char interrupted=0;

void exit_handler(int sig){
    interrupted=1;
}

int main(char** argc, int argv)
{
    FILE *fd;

    signal(SIGINT, exit_handler);
    signal(SIGTERM, exit_handler);

    while(1){
        fd=fopen("filepath.file","w");
        char *tst_str="Test String";
        if(fwrite(tst_str,1,11,fd)!=11)
            printf("Writing Error occured\n");

        if (fileno(fd)>=0){
            printf("FD Closed. (1)\n");
            fclose(fd);
        }
        if (fileno(fd)>=0){
            printf("FD Closed. (2)\n");
            fclose(fd);
        }
        if(interrupted)
            exit(0);
    }
    printf("Done.");
}

1

也许这些建议能帮到你:

你可以使用进程 ID 检查由你的进程打开的文件。

  1. pid_t getpid(void);,返回调用进程的进程 ID。
  2. 接下来将此 PID 传递给pfiles 命令。

第二:

在程序终止之前,你可以调用 int fcloseall (void);

此函数会导致关闭进程的所有打开流及其对应文件的连接。所有缓冲数据都将写入,任何缓冲输入都将被丢弃。如果成功关闭了所有文件,则 fcloseall 函数返回值为 0;如果检测到错误,则返回 EOF。


在Linux中,您可以从进程内部使用(并以编程方式检查)/proc/self/fd//proc/self/fdinfo/目录,或从外部使用/proc/$PID/fd/等。/proc/可以通过pfiles访问。 - Basile Starynkevitch
@BasileStarynkevitch,我在手册中读到pfiles命令读取proc文件系统。 - Grijesh Chauhan

0
int sig_int(int signo)
{
    if(fdOpen)
       fclose(fd);
    abort();
}

然后将此添加到main中:

static volatile fdOpen = 0;
static FILE *fd;//I guess
if(signal(SIGINT,sig_int) == SIG_ERR)
    printf("signal(SIGINT) error\n");

if((fd =fopen("your_file_name","flag_you_need")) != NULL)
    fdOpen = 1;

在 fcolse 之前执行此操作:
sigset_t newmask,oldmask;
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
sigprocmask(SIG_BLOCK, &newmask, &oldmask);//BLOCK SIGINT

在正常关闭文件后设置fdOpen = 0;//现在SIGINT不会中断我们了。

然后

sigprocmask(SIG_SETMASK,&oldmask,NULL);//RESET signal mask

所以我们做一些清理工作,然后在 sig_int 处退出程序。


这里唯一的问题是当正常的 fclose 执行后,但在 fdOpen = 0; 之前调用了 sig_int() - nullptr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接