Почему буферизация std :: ifstream «ломает» std :: getline при использовании LLVM? - PullRequest
12 голосов
/ 03 апреля 2019

У меня есть простое приложение на 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? (обновление: это вызвано libc ++)

Это ошибка?

Каким-то образом гарантируется, как это работает на g ++ стандартом C ++?

Как я могу заставить этот фрагмент кода работать правильно, используя clang++?

Обновление:

Кажется, это вызвано реализацией libc ++ getline(). Ссылки по теме:

Хотя вопросы все еще стоят.

1 Ответ

0 голосов
/ 14 апреля 2019

Как обсуждалось отдельно, решение boost::asio было бы лучше, но ваш вопрос конкретно о том, как getline блокирует, так что я поговорю с этим.

Проблема здесь в том, что std::ifstream на самом деле не создан для типа файла FIFO. В случае getline() он пытается выполнить буферизованное чтение, поэтому (в начальном случае) он решает, что в буфере недостаточно данных для достижения разделителя ('\n'), вызывает underflow() для базового streambuf, и это делает простое чтение для объема данных длины буфера. Это прекрасно работает для файлов, потому что длина файла в определенный момент времени является ощутимой, поэтому он может возвращать EOF, если данных недостаточно для заполнения буфера, и если данных достаточно, он просто возвращается с заполненным буфером. Однако в случае FIFO нехватка данных не обязательно означает EOF, поэтому он не возвращается, пока не завершится процесс записи в него (это ваша бесконечная команда sleep, которая удерживает ее открытой).

Более типичный способ сделать это - открыть и закрыть файл во время чтения и записи. Очевидно, что это пустая трата усилий, когда доступно что-то более функциональное, например poll() / epoll(), но я отвечаю на вопрос, который вы задаете.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...