【问题标题】:Why does the buffering of std::ifstream "break" std::getline when using LLVM?为什么使用 LLVM 时 std::ifstream 的缓冲会“中断”std::getline?
【发布时间】:2019-08-25 00:41:01
【问题描述】:

我有一个简单的 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;
    }
}

步骤:

  • 我创建了一个命名管道:mkfifo in

  • 我使用 g++ -std=c++11 test.cpp &amp;&amp; ./a.out 编译和运行 C++ 代码。

  • 我将数据输入到in 管道:

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(“嘿”)之后打印出第一条消息,并继续这样做,总是滞后一条消息并显示该消息之前的echo,而不是执行的那个。 最后一条消息只有在关闭管道后才会显示。

我发现在 macOS 上g++ 基本上是clang++,如 g++ --version 产生:“Apple LLVM 版本 10.0.1 (clang-1001.0.46.3)”。 使用 Homebrew 安装真正的 g++ 后,示例程序可以正常运行,就像在 Linux 上一样。

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

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

这是一个错误吗?

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

如何使用clang++ 使这段代码 sn-p 正常工作?

更新:

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

问题仍然存在。

【问题讨论】:

  • 语言标准没有说明任何输出的时间。 (它可能应该说一点,但不是关于这种多进程情况。)但是,POSIX 确实如此,而且这看起来确实有问题——如果你使用 DOS 换行符 (echo $'foo\r' &gt;in) 会发生什么?跨度>
  • @DavisHerring 是的,你是对的,我可能应该更准确地撰写我的话。当然,我的意思是std::getlines() 如果流有可用的换行符,则不会阻塞。尝试使用 DOS 换行符产生了相同的结果。
  • 奇怪的是,我看到较旧的“Apple LLVM 版本 7.0.2 (clang-700.1.81)”具有所需的行为,_LIBCPP_VERSION 为 1101。
  • 在链接的错误报告中,我发现了以下响应:“问题实际上出在 basic_filebuf<_chart _traits>::underflow() 中。在第 595 行附近,它调用 fread 来填充 filebuf 的缓冲区. (默认情况下,4096 字节)。此读取挂起,直到它可以读取整个缓冲区已满。当它与文件交谈时,文件没有那么多数据,它会得到一个短读取。当它是与管道交谈时,它只是等到有那么多数据可用。”这似乎表明这确实是一个错误。当我有时间时,我可能应该检查我们尝试过的两个版本中的引用代码。
  • 我很欣赏这个建议,因为它是一个很好的建议。事实上,我之前曾使用过 boost 的 asyncio,是的,这将是一般 IPC 的优雅解决方案。但是,问题不是“如何构建可扩展且灵活的 IPC 解决方案”。与大多数项目一样,我有一些超出我能力范围的需求。问题是关于 std::getline() 在 libc++ 中的奇怪行为,而不是我从命名管道读取直到分隔符的原因。

标签: c++ macos pipe clang named-pipes


【解决方案1】:

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

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

执行此操作的更典型方法是让编写器在读取和写入时打开和关闭文件。当poll()/epoll() 等功能更强大的功能可用时,这显然是在浪费精力,但我正在回答您提出的问题。

【讨论】:

    【解决方案2】:

    我通过将 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] = '\0';
            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++。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-10-08
      • 1970-01-01
      • 2013-05-09
      • 2013-03-17
      • 1970-01-01
      • 1970-01-01
      • 2013-09-10
      • 1970-01-01
      相关资源
      最近更新 更多