我需要为我的一个C程序关闭stdout和stderr。如果不退出程序执行,怎么可能?
你有什么尝试? fclose
不工作吗?
你可以:
fclose(stdout);
fclose(stderr);
对于任何想知道你为什么要这样做的人来说,这对于Unix上的守护进程/服务进程来说是一个相当普遍的任务。
但是,您应该知道关闭文件描述符可能会产生意想不到的后果:
fopen
文件描述符(至少在Linux上)将替换fd 1,即stdout。随后使用此代码的任何代码都将写入此文件,这可能与您的意图不同。FILE*
指针的评论。特别:
如果你在Linux下写一个封闭的fd,你会收到一个错误,但是:
如果你使用的c库函数使用stdout
或stderr
(这是FILE*
指针(参见their definition),那么在FILE*
关闭时写入这些是未定义的行为。这可能会以意想不到的方式崩溃你的程序,而不是总是在也有错误。见undefined behaviour。快速,单行的解决方案是freopen()
在Linux / OSX或/dev/null
在Windows上说/dev/console
,nul
。或者,您可以根据需要使用特定于平台的实现重新打开文件描述符/句柄。
如果要阻止应用程序写入控制台,则:
#include <stdio.h>
int main()
{
fprintf(stdout, "stdout: msg1\n");
fprintf(stderr, "stderr: msg1\n");
fclose(stdout);
fprintf(stdout, "stdout: msg2\n"); // Please read the note setion below
fprintf(stderr, "stderr: msg2\n");
fclose(stderr);
fprintf(stdout, "stdout: msg3\n");
fprintf(stderr, "stderr: msg3\n");
}
输出:
stdout: msg1
stderr: msg1
stderr: msg2
注意:文件关闭后任何尝试使用FILE指针都是错误的。我在这种情况下这样做只是为了说明关闭这些文件描述符可能会对您的应用程序做什么。
警告:我根本没有C经验,但最近读了一张幻灯片,由RedHat员工和GNUlib维护人员Jim Meyering直接回答这个问题:https://www.gnu.org/ghm/2011/paris/slides/jim-meyering-goodbye-world.pdf。我只是总结一下。
将closeout.c及其依赖项从GNUlib获取到源代码并调用
atexit(close_stdout);
作为你的主要第一线。
首先,有人提醒警告,引用POSIX:
因为在调用fclose()之后对流的任何使用导致了未定义的行为,所以除了在进程终止之前,不应该在stdin,stdout或stderr上使用fclose()......如果有任何atexit()应用程序注册的处理程序,在最后一个处理程序完成之前不应该调用fclose()。一旦fclose()用于关闭stdin,stdout或stderr,就没有标准方法可以重新打开这些流。
在文件描述符STDIN_FILENO,STDOUT_FILENO或STDERR_FILENO上使用close()后应立即执行重新打开这些文件描述符的操作。 ......此外,close()后跟重新打开操作(例如open(),dup()等)不是原子的; dup2()应该用于更改标准文件描述符。
关闭流而不处理其错误并不健壮,对于stdout和stderr也是如此。以下是您需要处理的错误列表:
fclose(stdout)
ferror(stdout)
a.k.a.上一个错误__fpending(stdout)
a.k.a.东西没有红晕正如GNUlib在close-stream.c中实现的那样处理这些错误,引用如下。
int
close_stream (FILE *stream)
{
const bool some_pending = (__fpending (stream) != 0);
const bool prev_fail = (ferror (stream) != 0);
const bool fclose_fail = (fclose (stream) != 0);
/* Return an error indication if there was a previous failure or if
fclose failed, with one exception: ignore an fclose failure if
there was no previous error, no data remains to be flushed, and
fclose failed with EBADF. That can happen when a program like cp
is invoked like this 'cp a b >&-' (i.e., with standard output
closed) and doesn't generate any output (hence no previous error
and nothing to be flushed). */
if (prev_fail || (fclose_fail && (some_pending || errno != EBADF)))
{
if (! fclose_fail)
errno = 0;
return EOF;
}
return 0;
}
注意:__fpending
对于glibc来说是特殊的,可能不是便携式的。 OTOH,它是on the way to be standardized作为fpending
。
P.S:
我只想将stdout和stderr输出定向到日志文件而不是控制台。
如果你根据http://cloud9.hedgee.com./scribbles/daemon#logging写一个守护进程,这不是关闭stdout和stderr的好理由。您应该让守护程序管理器(例如守护程序工具,runit,s6,nosh,OpenRC和systemd)处理重定向。
但是,您仍应关闭程序最终写入的任何流以检查错误。从close-stream.c引用:
如果程序向STREAM写入任何内容,该程序应该关闭STREAM并确保它在退出之前成功。否则,假设您最终检查了对STREAM进行显式写入的每个函数的返回状态。最后一个printf可以成功写入内部流缓冲区,但是当它尝试写出缓冲数据时,fclose(STREAM)仍然可能失败(例如,由于磁盘已满错误)。因此,您将留下不完整的输出文件,并且违规程序将成功退出。即使调用fflush也不总是足够的,因为一些文件系统(NFS和CODA)缓冲写入/刷新的数据直到实际的关闭调用。
此外,检查写入STREAM的每个调用的返回值是浪费的 - 只需让内部流状态记录失败。这就是下面的恐怖测试。
实际上你也可以使用close
函数:
#include<unistd.h> // close
#include<stdio.h> // STDOUT_FILENO,STDERR_FILENO
...
close(STDOUT_FILENO);
close(STDERR_FILENO);
...