检查 FILE* 是否为标准输出;便携的?

check if FILE* is stdout; portable?

我目前正在编写一段代码,其用途是这样的:

program input.txt output.txt

program input.txt

在这种情况下它默认为 stdout

这是我现在的代码(在main()内):

FILE *outFile;
if (argc < 3) {
        outFile = stdout;
    } else {
        fprintf(stdout, "Will output to file %s\n", argv[2]);
        outFile = fopen(argv[2], "w");
        if (outFile == NULL) {
            fprintf(stderr, "ERR: Could not open file %s. Defaulting to stdout\n", argv[2]);
            outFile = stdout;
        }
    }
/* ... write stuff to outFile... */
if (argc < 3 && outFile != stdout) {
        fclose(outFile);
    }

这些是我的顾虑:首先,提供时是否会成功打开和关闭outFile?另外,这会成功 而不是 关闭标准输出吗?如果我关闭 stdout 会发生什么不好的事情吗?

另外,这个是便携的吗?我使用 gcc 进行编译,但该项目将由一位教授使用 Windows.

进行评估

抱歉,如果这个问题有点乱。我来自Python,不是CS专业的(我学的是数学)。

是的,便携,没问题。

是的,它是便携式的。您分配了 outfile = stdout,因此只要您不在程序的其他地方重新分配它们中的任何一个,它们就会相等。

您实际上也不需要 argc < 3 测试——这两个条件应该始终相同,因为您只在条件为真时才进行赋值。

在任何将重要数据写入 stdout 的程序中,您 应该 在退出前立即关闭 stdout,以便您可以检查和报告延迟写入错误。 (延迟写入错误是设计错误;fcloseclose 应该不可能失败。但我们坚持使用它们。)

通常的构造是,在 main

的最后
if (ferror(stdout) || fclose(stdout)) {
    perror("stdout: write error");
    return 1;
}
return 0;

有些程序也会在其中插入 fflush,但 ISO C 需要 fclose 才能执行 fflush,因此 不应该 是必要的。这个构造是完全可移植的。

重要的是,这是您在退出前做的最后一件事。图书馆通常认为 stdout 永远不会关闭,因此如果您在关闭 stdout 后调用它们,它们可能会出现故障。 stdinstderr 这样也很麻烦,但我还没有遇到 想要 关闭它们的情况。

有时确实会发生您想在程序完全完成之前关闭 stdout 的情况。在那种情况下,您实际上应该让 FILE 保持打开状态,但关闭底层 "file descriptor" 并将其替换为虚拟对象。

int rfd = open("/dev/null", O_WRONLY);
if (rfd == -1) perror_exit("/dev/null");
if (fflush(stdout) || close(1)) perror_exit("stdout: write error");
dup2(rfd, 1);
close(rfd);

此构造不可移植到 Windows。有一个等价物,但我不知道它是什么。它也不是线程安全的:另一个线程可以在 closedup2 操作之间调用 open 并被分配 fd 1,或者它可以尝试向 stdout 写入一些东西在那个 window 并得到一个虚假的写入错误。为了线程安全,您必须复制旧的 fd 1 并通过该句柄关闭它:

// These allocate new fds, which can always fail, e.g. because
// the program already has too many files open.
int new_stdout = open("/dev/null", O_WRONLY);
if (new_stdout == -1) perror_exit("/dev/null");
int old_stdout = dup(1);
if (old_stdout == -1) perror_exit("dup(1)");

flockfile(stdout);
if (fflush(stdout)) perror_exit("stdout: write error");
dup2 (new_stdout, 1); // cannot fail, atomically replaces fd 1
funlockfile(stdout);

// this close may receive delayed write errors from previous writes
// to stdout
if (close (old_stdout)) perror_exit("stdout: write error");

// this close cannot fail, because it only drops an alternative
// reference to the open file description now installed as fd 1
close (new_stdout);

操作顺序很关键:opendupfflush 调用必须在 dup2 调用之前发生,两个 close 调用都必须发生在 dup2 调用之后,stdout 必须从 fflush 调用之前一直锁定到 dup2 调用之后。

其他可能的并发症,作为练习处理:

  • 当您不想在出错时停止整个程序时,清理临时 fds 和锁定错误
  • 如果线程可能在操作中途被取消
  • 如果并发线程可能会调用 forkexecve 中间操作