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

Jam*_* Ko 2 c windows io stdio fread

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

我正在尝试base64在C中重新实现一个从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;
}
Run Code Online (Sandbox Code Playgroud)

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

当我将二进制文件的内容传送到此应用程序的stdin时,它只会读取文件中总字节数的一小部分.例如:

$ 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
Run Code Online (Sandbox Code Playgroud)

正如您所看到的那样,/bin/echo长度为28352个字节,但仅处理了大约2400个字节.我相信原因是因为stdin不被认为是二进制文件,因此某些控制字符(如链接帖子的答案中提到的Control-Z)过早地表示EOF.

我看了一下base64的源代码,看起来他们正在使用xfreopen(这只是一个包装器freopen)告诉fread将stdin解释为二进制.所以我继续在while循环之前做到了:

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

但是,现在我的应用程序始终退出:

$ cat /bin/echo | my_base64_program
freopen failed. error: Invalid argument
Run Code Online (Sandbox Code Playgroud)

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

Jon*_*ler 6

为什么freopen()一般会失败

C标准说:

如果filename是空指针,则该freopen函数尝试将stream指定的模式更改为指定的模式mode,就好像当前与该流关联的文件的名称已被使用一样.它是实现定义的,允许更改模式(如果有),以及在什么情况下.

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

新模式必须与最初打开流的模式兼容:

  • 最初以模式"r"打开的流只能使用相同的模式重新打开.
  • 最初以模式"a"打开的流可以使用相同的模式或模式"w"重新打开.
  • 最初以模式"w"打开的流可以使用相同的模式或模式"a"重新打开.
  • 最初以模式"r +","w +"或"a +"打开的流可以使用任何模式重新打开.

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

为什么freopen()在Windows上失败?

但是,你在Windows上.您需要学习如何查找和阅读文档.对于我正在寻找的任何功能,我使用术语"site:msdn.microsoft.com freopen"的谷歌搜索.该特定搜索产生了说明的手册freopen():

如果path,mode或者stream是空指针,或者如果filename是空字符串,则这些函数将调用无效参数处理程序,如参数验证中所述.如果允许继续执行,则这些函数将errno设置为EINVAL并返回NULL.

这是记录在案的行为:它也是您所看到的行为.系统手册很有帮助.它基本上说"你不应该".

如何在Windows上修复标准输入的输入模式

我注意到,在我回答刚才的问题,我指着_setmode():

但是,您更有可能需要_setmode():

_setmode(_fileno(stdin), O_BINARY);
Run Code Online (Sandbox Code Playgroud)

这是在答案,给出的建议问题deamentiaemundi指出.

我顺便提一下,微软的手册页setmode()说:

不推荐使用此POSIX函数.请改用符合ISO C++标准_setmode.

这是一个好奇的评论,因为POSIX首先没有标准化函数setmode().

您可以找到Microsoft的文档fileno().它也有关于POSIX的spiel(但这次它是准确的; POSIX确实指定fileno())并引用你_fileno().

  • @HarryJohnston:我认为这是相关的。如果你想给出一个包含独特的新信息但不包含这些信息的答案,请成为我的客人。请注意,我在答案中所说的只是 Microsoft 指定它已被弃用(在他们的系统上,因为这是他们正在记录的内容),并且声称 `setmode()` 是 POSIX 函数是不准确的;它没有被 POSIX 标准化。我认为这没有什么争议。(哦,另一个答案讨论的是 `setmode()` 而不是 `_setmode()`,所以解释这种差异是相关的。) (2认同)