为什么使用 LLVM 时 std::ifstream "break" std::getline 的缓冲?

Why does the buffering of std::ifstream "break" std::getline when using LLVM?

我有一个简单的 C++ 应用程序,它应该从 POSIX 命名管道中读取行:

#include<iostream>
#include<string>
#include<fstream>

int main() {
    std::ifstream pipe;
    pipe.open("in");

    std::string line;
    while (true) {
        std::getline(pipe, line);
        if (pipe.eof()) {
            break;
        }
        std::cout << line << std::endl;
    }
}

步骤:

sleep infinity > in &  # keep pipe open, avoid EOF
echo hey > in
echo cats > in
echo foo > in
kill %1                # this closes the pipe, C++ app stops on EOF

在 Linux 下执行此操作时,应用程序会按预期在每个 echo 命令后成功显示输出 (g++ 8.2.1)。

在 macOS 上尝试整个过程时,输出仅在关闭管道后显示(即 kill %1 后)。 我开始怀疑某种缓冲问题,所以我试过像这样禁用它:

std::ifstream pipe;
pipe.rdbuf()->pubsetbuf(0, 0);
pipe.open("out");

通过此更改,应用程序在第一个 echo 之后不输出任何内容,然后在第二个 echo ("hey") 之后打印出第一条消息,并一直如此滞后一条消息并显示前一个 echo 的消息而不是已执行的消息。 最后一条消息仅在关闭管道后显示。

我发现在 macOS 上 g++ 基本上是 clang++,因为 g++ --version 产量:"Apple LLVM version 10.0.1 (clang-1001.0.46.3)"。 使用 Homebrew 安装真正的 g++ 后,示例程序可以运行,就像在 Linux.

上一样

出于各种原因,我正在构建一个基于命名管道的简单 IPC 库,因此目前对我来说,正确工作几乎是一个要求。

使用 LLVM 时出现这种奇怪行为的原因是什么?(更新:这是由 libc++ 引起的)

这是一个错误吗?

C++ 标准是否以某种方式保证了这种在 g++ 上的工作方式?

如何使用 clang++ 使此代码片段正常工作?

更新:

这似乎是由getline()的libc++实现引起的。 相关链接:

虽然问题仍然存在。

正如单独讨论的那样,boost::asio 解决方案是最好的,但你的问题具体是关于 getline 是如何阻塞的,所以我将讨论它。

这里的问题是 std::ifstream 并不是真正为 FIFO 文件类型制作的。在 getline() 的情况下,它正在尝试进行缓冲读取,因此(在初始情况下)它决定缓冲区没有足够的数据到达分隔符 ('\n'),调用 underflow() 在底层 streambuf 上,它对缓冲区长度的数据量进行简单读取。这对文件非常有用,因为文件在某个时间点的长度是可知的长度,所以如果没有足够的数据来填充缓冲区,它可以 return EOF,如果有足够的数据,它只是 returns 与填充的缓冲区。但是,对于 FIFO,运行 数据不足并不一定意味着 EOF,因此它不会 return 直到写入它的进程关闭(这是你的无限 sleep 保持它打开的命令)。

一种更典型的方法是编写器在读取和写入文件时打开和关闭文件。当 poll()/epoll() 等功能更强大的东西可用时,这显然是浪费精力,但我正在回答你问的问题。

我通过将 POSIX getline() 包装在一个简单的 C API 中并从 C++ 中简单地调用它来解决这个问题。 代码是这样的:

typedef struct pipe_reader {
    FILE* stream;
    char* line_buf;
    size_t buf_size;
} pipe_reader;

pipe_reader new_reader(const char* pipe_path) {
    pipe_reader preader;
    preader.stream = fopen(pipe_path, "r");
    preader.line_buf = NULL;
    preader.buf_size = 0;
    return preader;
}

bool check_reader(const pipe_reader* preader) {
    if (!preader || preader->stream == NULL) {
        return false;
    }
    return true;
}

const char* recv_msg(pipe_reader* preader) {
    if (!check_reader(preader)) {
        return NULL;
    }
    ssize_t read = getline(&preader->line_buf, &preader->buf_size, preader->stream);
    if (read > 0) {
        preader->line_buf[read - 1] = '[=10=]';
        return preader->line_buf;
    }
    return NULL;
}

void close_reader(pipe_reader* preader) {
    if (!check_reader(preader)) {
        return;
    }
    fclose(preader->stream);
    preader->stream = NULL;
    if (preader->line_buf) {
        free(preader->line_buf);
        preader->line_buf = NULL;
    }
}

这适用于 libc++ 或 libstdc++。