【发布时间】:2018-04-21 22:10:26
【问题描述】:
TL;DR
使用libc++ 版本的getline 函数的程序在从管道读取输入时会阻塞,直到管道缓冲区已满。
对于libstdc++ 版本的getline 函数NOT也是如此:这里的函数会立即读取并在输入行可用时立即返回。
我应该期待libstdc++ 和libc++ 之间的这种行为差异吗? [编辑:我不是在这里征求意见,我对管道的了解不够,也不太了解实现 C++ 标准库的困难。对我来说,这种行为上的差异确实令人惊讶,但也许有人知道得更好,可以向我保证这种差异是可以预料的,也许这只是一个实现细节?]
更重要的是,我该怎么做才能让libc++ 表现得像libstdc++ 那样?也就是说,getline 函数不应该等到管道缓冲区满了,它应该在输入行可用时立即返回。
下面的代码示例展示了我如何读取和写入管道。
环境
- macOS 10.13.1(High Sierra)
- Xcode 9.1
- Apple LLVM 版本 9.0.0 (clang-900.0.38)
我怀疑问题不仅限于 macOS,但我没有可以用来测试的带有clang 的 Linux 开发系统。
考试准备
打开三个shell,我们称它们为A、B和C。
在 shell A 中:创建一个新文件 pipe-test.cpp 并从下面添加源代码。使用libstdc++ 编译一次源代码,使用libc++ 编译一次源代码:
g++ -stdlib=libstdc++ -o pipe-test-libstdc++ pipe-test.cpp
g++ -stdlib=libc++ -o pipe-test-libc++ pipe-test.cpp
在 shell A 中:创建两个管道:
mkfifo input-pipe output-pipe
测试1,使用libstdc++版本的程序
- 在 shell A 中,运行以下命令:
pipe-test-libstdc++ input-pipe output-pipe - 在 shell B 中,运行以下命令:
cat output-pipe - 在 shell C 中,运行以下命令:
cat >input-pipe - 在 shell C 中,键入“foo”行并按 ENTER
- 切换到 shell B:您将看到字符串“foo”。
- 发生了什么?在 shell A 中运行的程序已使用
getline函数从输入管道中读取了“foo”行,并立即将该行打印到输出管道。 - 在 shell C 中,键入 CTRL+D。测试到此结束,所有的 shell 现在都应该回到命令行了。
测试2,使用libc++版本的程序
- 在 shell A 中,运行以下命令:
pipe-test-libc++ input-pipe output-pipe - 在 shell B 中,运行以下命令:
cat output-pipe - 在 shell C 中,运行以下命令:
cat >input-pipe - 在 shell C 中,键入“foo”行并按 ENTER
- 切换到 shell B:您将不会看到字符串“foo”。
- 发生了什么?在 shell A 中运行的程序仍处于阻塞状态,
getline函数尚未从输入管道接收到“foo”行,因为该管道的缓冲区尚未满。 - 在 shell C 中,键入 CTRL+D。
- 切换到 shell B:现在您将看到字符串“foo”。
- 请注意,除了键入 CTRL+D,您还可以将大量文本粘贴到 shell C 中。一旦管道的缓冲区已满,
getline函数将开始逐行读取输入,直到清空管道的缓冲区。
源代码
#include <string>
#include <iostream>
#include <fstream>
int main(int argc, char** argv)
{
if (argc != 3)
{
std::cout
<< "Usage: pipe-test /path/to/input-pipe /path/to/output-pipe"
<< std::endl;
return 1;
}
std::string pathToInputPipe = argv[1];
std::string pathToOutputPipe = argv[2];
std::cout
<< "Input pipe = " << pathToInputPipe << std::endl
<< "Output pipe = " << pathToOutputPipe << std::endl;
std::ifstream inputPipeStream;
inputPipeStream.open(pathToInputPipe.c_str());
if (! inputPipeStream)
{
std::cout
<< "Failed to open input pipe " << pathToInputPipe
<< std::endl;
return 1;
}
else
{
std::cout
<< "Input pipe: result of eof() = " << inputPipeStream.eof() << std::endl
<< "Input pipe: result of bad() = " << inputPipeStream.bad() << std::endl
<< "Input pipe: result of good() = " << inputPipeStream.good() << std::endl
<< "Input pipe: result of fail() = " << inputPipeStream.fail() << std::endl;
}
std::ofstream outputPipeStream;
outputPipeStream.open(pathToOutputPipe.c_str());
if (! outputPipeStream)
{
std::cout
<< "Failed to open output pipe " << pathToOutputPipe
<< std::endl;
return 1;
}
else
{
std::cout
<< "Output pipe: result of eof() = " << outputPipeStream.eof() << std::endl
<< "Output pipe: result of bad() = " << outputPipeStream.bad() << std::endl
<< "Output pipe: result of good() = " << outputPipeStream.good() << std::endl
<< "Output pipe: result of fail() = " << outputPipeStream.fail() << std::endl;
}
int lineNumber = 0;
while (true)
{
lineNumber++;
std::string line;
bool getlineFailResult = getline(inputPipeStream, line).fail();
if (getlineFailResult)
{
std::cout << "Failed to read stream, stopping" << std::endl;
break;
}
else
{
std::cout << "Received line " << lineNumber << ": " << line << std::endl;
outputPipeStream << line << std::endl;
outputPipeStream.flush();
}
}
return 0;
}
【问题讨论】:
-
快速提问:您是否在每次测试前删除了管道?
-
我的 GCC 版本是
gcc version 6.3.0 20170519 (Ubuntu/Linaro 6.3.0-18ubuntu2~16.04)。我无法使用标志-stdlib=libstdc++和-stdlib=libc++,GCC 只是说它们无法识别。它建议-static-libc++和-static-libgcc(分别)代替。使用推荐的标志运行这两个示例后,我得到了相同的行为(这是-stdlib=libstdc++的预期行为)。没有不同。你用的是什么编译器版本? -
我能够在 OSX High-Sierra 10.13.1 上重现这一点:
Apple LLVM version 9.0.0 (clang-900.0.37) Target: x86_64-apple-darwin17.2.0 Thread model: posix.. 即使在删除管道并尝试使用新管道之后。奇怪.. -
有趣,我尝试使用 clang 版本
clang version 5.0.1-svn316607-1~exp1 (branches/release_50),我看到了相同的输出。我想问题是,在clang上libc++和libstdc++有什么区别。我还应该提到我在 Linux Ubuntu 上
标签: c++ getline libstdc++ libc++