我想将 stdin 解释为二进制文件。为什么 freopen 在 Windows 上失败?

I want to interpret stdin as a binary file. Why is freopen failing on Windows?

TL;DR: 为什么 freopen(NULL, "rb", stdin) 总是在 Windows 上失败?

我正在尝试在 C 中重新实现一个 base64 编码器,该编码器从 stdin 获取输入并将编码后的等价物输出到 stdout。我有一个问题 ,其中 fread 过早地发出 EOF 信号。这是我的主要方法:

int main(void)
{
    unsigned char buffer[BUFFER_SIZE];
    unsigned char base64_buffer[BASE64_BUFFER];

    while (1)
    {
        TRACE_PUTS("Reading in data from stdin...");
        size_t read = fread(buffer, 1, sizeof(buffer), stdin); /* Read the data in using fread(3) */

        /* Process the buffer */

        TRACE_PRINTF("Amount read: %zu\n", read);
        TRACE_PUTS("Beginning base64 encode of buffer");
        size_t encoded = base64_encode(buffer, read, base64_buffer, sizeof(base64_buffer));

        /* Write the data to stdout */
        TRACE_PUTS("Writing data to standard output");
        ...

        if (read < sizeof(buffer))
        {
            break; /* We reached EOF or had an error during the read */
        }
    }

    if (ferror(stdin))
    {
        /* Handle errors */
        fprintf(stderr, "%s\n", "There was a problem reading from the file.");
        exit(1);
    }

    puts(""); /* Output a newline before finishing */

    return 0;
}

本质上,它使用 fread 从 stdin 读取数据,编码为 base64,将其写入 stdout,然后在循环结束时检查是否已达到 EOF。

当我将二进制文件的内容通过管道传输到此应用程序的标准输入时,它只会读取文件总字节数的一小部分。例如:

$ cat /bin/echo | my_base64_program >/dev/null # only view the trace output
TRACE: C:/Users/James/Code/c/base64/main.c:23: Reading in data from stdin...
TRACE: C:/Users/James/Code/c/base64/main.c:28: Amount read: 600
TRACE: C:/Users/James/Code/c/base64/main.c:29: Beginning base64 encode of buffer
TRACE: C:/Users/James/Code/c/base64/main.c:43: Writing data to standard output
TRACE: C:/Users/James/Code/c/base64/main.c:23: Reading in data from stdin...
TRACE: C:/Users/James/Code/c/base64/main.c:28: Amount read: 600
TRACE: C:/Users/James/Code/c/base64/main.c:29: Beginning base64 encode of buffer
TRACE: C:/Users/James/Code/c/base64/main.c:43: Writing data to standard output
TRACE: C:/Users/James/Code/c/base64/main.c:23: Reading in data from stdin...
TRACE: C:/Users/James/Code/c/base64/main.c:28: Amount read: 600
TRACE: C:/Users/James/Code/c/base64/main.c:29: Beginning base64 encode of buffer
TRACE: C:/Users/James/Code/c/base64/main.c:43: Writing data to standard output
TRACE: C:/Users/James/Code/c/base64/main.c:23: Reading in data from stdin...
TRACE: C:/Users/James/Code/c/base64/main.c:28: Amount read: 569
TRACE: C:/Users/James/Code/c/base64/main.c:29: Beginning base64 encode of buffer
TRACE: C:/Users/James/Code/c/base64/main.c:43: Writing data to standard output

$ cat /bin/echo | wc -c
28352

如您所见,/bin/echo 有 28352 个字节长,但只有约 2400 个正在处理。我相信原因是因为 stdin 未被视为二进制文件,因此某些控制字符(如链接 post 的回答中提到的 Control-Z)过早地发出 EOF 信号。

我看了一下 base64 source code,他们似乎在使用 xfreopen(这只是 freopen 的包装)告诉 fread将标准输入解释为二进制。所以我在 while 循环之前继续这样做:

if (!freopen(NULL, "rb", stdin))
{
    fprintf(stderr, "freopen failed. error: %s\n", strerror(errno));
    exit(1);
}

但是,现在我的应用程序总是在那个时候退出:

$ cat /bin/echo | my_base64_program
freopen failed. error: Invalid argument

那么为什么 freopen 在那个时候失败了,而它适用于 base64?如果相关的话,我在 Windows 上使用 MinGW-w64 和 GCC。

为什么 freopen() 通常会失败

C 标准说:

If filename is a null pointer, the freopen function attempts to change the mode of the stream to that specified by mode, as if the name of the file currently associated with the stream had been used. It is implementation-defined which changes of mode are permitted (if any), and under what circumstances.

据推测,您的实施不允许您尝试进行的更改。例如,在 Mac OS X 上,freopen() 的手册页添加:

The new mode must be compatible with the mode that the stream was originally opened with:

  • Streams originally opened with mode "r" can only be reopened with that same mode.
  • Streams originally opened with mode "a" can be reopened with the same mode, or mode "w".
  • Streams originally opened with mode ``w'' can be reopened with the same mode, or mode "a".
  • Streams originally opened with mode "r+", "w+", or "a+" can be reopened with any mode.

话虽如此,在 Mac OS X 上(无论如何 b 都是空操作),你会没事的。

为什么 freopen() 在 Windows 上特别失败

但是,您在 Windows。您需要学习如何查找和阅读文档。我使用 Google 搜索词 'site:msdn.microsoft.com freopen' 来查找我要查找的任何函数。该特定搜索会生成 freopen() 的手册,其中显示:

If path, mode, or stream is a null pointer, or if filename is an empty string, these functions invoke the invalid parameter handler, as described in Parameter Validation. If execution is allowed to continue, these functions set errno to EINVAL and return NULL.

这是记录在案的行为:这也是您所看到的行为。您的系统手册很有帮助。它基本上说 "thou shalt not".

如何在Windows

上修复标准输入的输入模式

我注意到在我的 to your 中,我指向 _setmode():

However, it is more likely that you need _setmode():

_setmode(_fileno(stdin), O_BINARY);

这是对 question that deamentiaemundi 指向的答案中给出的建议。

我顺便注意到 setmode() 的 Microsoft 手册页说:

This POSIX function is deprecated. Use the ISO C++ conformant _setmode instead.

这是一个奇怪的评论,因为首先 POSIX does not standardize a function setmode()

您可以找到 fileno(). It too has the spiel about POSIX (but this time it is accurate; POSIX does specify fileno()) and refers you to _fileno() 的 Microsoft 文档。