Почему буферизация std :: ifstream "ломает" std :: getline при использовании 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;
}
}
шаги:
-
Я создаю именованный канал: mkfifo in
.
-
Я компилирую и запускаю код C++, используя g++ -std=C++11 test.cpp &&./a.out
.
-
Я передаю данные 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)". После установки настоящего g++ с использованием Homebrew, пример программы работает так же, как и в Linux.
Я строю простую библиотеку IPC, построенную на именованных каналах по разным причинам, поэтому правильная работа с ней в значительной степени является требованием для меня на данный момент.
Что вызывает это странное поведение при использовании LLVM? (обновление: это вызвано lib C++)
Это ошибка?
Каким-то образом гарантируется, как это работает на g++ стандартом C++?
Как я могу заставить этот фрагмент кода работать должным образом, используя clang++
?
Обновить:
По-видимому, это вызвано реализацией lib C++ функции getline()
. Ссылки по теме:
Вопросы все еще стоят, хотя.
Ответы
Ответ 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;
}
}
Это хорошо работает против lib C++ или libstd C++.