Почему бит бит установлен, когда eof найден при чтении? - PullRequest
19 голосов
/ 21 июля 2011

Я читал, что <fstream> предшествует <exception>. Игнорируя тот факт, что исключения на fstream не очень информативны, у меня есть следующий вопрос:

Возможно включить исключения для файловых потоков, используя метод exceptions().

ifstream stream;
stream.exceptions(ifstream::failbit | ifstream::badbit);
stream.open(filename.c_str(), ios::binary);

Любая попытка открыть несуществующий файл, файл без правильных разрешений или любую другую проблему ввода-вывода приведет к исключению. Это очень хорошо, используя стиль напористого программирования. Файл должен был быть там и быть читаемым. Если условия не выполняются, мы получаем исключение. Если бы я не был уверен, можно ли открыть файл безопасно, я мог бы использовать другие функции для его проверки.

Но теперь предположим, что я пытаюсь прочитать в буфер, например так:

char buffer[10];
stream.read(buffer, sizeof(buffer)); 

Если поток обнаруживает конец файла перед заполнением буфера, поток решает установить failbit, и возникает исключение, если они были включены. Зачем? какой в ​​этом смысл? Я мог бы проверить, что просто проверял eof() после прочтения:

char buffer[10];
stream.read(buffer, sizeof(buffer));
if (stream.eof()) // or stream.gcount() != sizeof(buffer)
    // handle eof myself

Этот выбор дизайна не позволяет мне использовать стандартные исключения в потоках и вынуждает меня создавать собственную обработку исключений для разрешений или ошибок ввода-вывода. Или я что-то упустил? Есть ли выход? Например, могу ли я легко проверить, могу ли я читать sizeof(buffer) байтов в потоке перед этим?

Ответы [ 3 ]

23 голосов
/ 21 июля 2011

Сбойный бит предназначен для того, чтобы поток мог сообщить, что какая-то операция не завершилась успешно.Это включает ошибки, такие как невозможность открыть файл, попытка прочитать данные, которые не существуют, и попытка прочитать данные неправильного типа.

Конкретный случай, о котором вы спрашиваете, перепечатывается здесь:

char buffer[10];
stream.read(buffer, sizeof(buffer)); 

Ваш вопрос заключается в том, почему сбой устанавливается, когда достигается конец файла до того, как все входные данные прочитаны.Причина в том, что это означает, что операция чтения завершилась неудачно - вы попросили прочитать 10 символов, но в файле недостаточно символов.Следовательно, операция не завершилась успешно, и поток сообщает об ошибке, чтобы сообщить вам об этом, даже если доступные символы будут прочитаны.

Если вы хотите выполнить операцию чтения, в которой вы хотите прочитать до некоторого количества символов, вы можете использовать функцию-член readsome:

char buffer[10];
streamsize numRead = stream.readsome(buffer, sizeof(buffer)); 

Эта функция будет читать символы до конца файла, но в отличие от read она неНе устанавливайте failbit, если конец файла достигнут до того, как символы будут прочитаны.Другими словами, он говорит: «Попробуй прочитать столько символов, но это не ошибка, если не можешь. Просто дай мне знать, сколько ты читаешь».Это контрастирует с read, который говорит: «Я хочу точно столько символов, и это ошибка, если вы не можете это сделать».

РЕДАКТИРОВАТЬ :Важная деталь, которую я забыл упомянуть, это то, что eofbit может быть установлен без запуска failbit.Например, предположим, что у меня есть текстовый файл, который содержит текст

137

без каких-либо символов новой строки или конечных пробелов после этого.Если я напишу этот код:

ifstream input("myfile.txt");

int value;
input >> value;

Тогда в этот момент input.eof() вернет true, потому что при чтении символов из файла поток попадает в конец файла, пытаясь увидеть, были ли какие-либо другиесимволы в потоке.Однако input.fail() будет , а не вернет true, потому что операция прошла успешно - мы действительно можем прочитать целое число из файла.

Надеюсь, это поможет!

2 голосов
/ 22 июля 2013

Прямое использование нижележащего буфера, кажется, помогает:

char buffer[10];
streamsize num_read = stream.rdbuf()->sgetn(buffer, sizeof(buffer));
1 голос
/ 23 марта 2014

Улучшая @ отсутствие ответа, он следует методу readeof(), который делает то же самое с read(), но не устанавливает failbit на EOF.Также были проверены реальные сбои чтения, такие как прерванная передача при жестком удалении USB-накопителя или сбрасывание ссылки в сетевом доступе.Он был протестирован на Windows 7 с VS2010 и VS2013 и на Linux с GCC 4.8.1.В linux было опробовано только удаление USB-флешки.

#include <iostream>
#include <fstream>
#include <stdexcept>

using namespace std;

streamsize readeof(istream &stream, char *buffer, streamsize count)
{
    streamsize offset = 0;
    streamsize reads;
    while (!stream.eof())
    {
        // Check also for already failed streams
        if (stream.fail())
            throw runtime_error("Stream I/O error while reading");

        if (count == 0)
            return offset;

        // This consistently fails on gcc (linux) 4.8.1 with failbit set on read
        // failure. This apparently never fails on VS2010 and VS2013 (Windows 7)
        reads = stream.rdbuf()->sgetn(buffer + offset, count);

        // This rarely sets failbit on VS2010 and VS2013 (Windows 7) on read
        // failure of the previous sgetn()
        (void)stream.rdstate();

        // On gcc (linux) 4.8.1 and VS2010/VS2013 (Windows 7) this consistently
        // sets eofbit when stream is EOF for the conseguences  of sgetn(). It
        // should also throw if exceptions are set, or return on the contrary,
        // and previous rdstate() restored a failbit on Windows. On Windows most
        // of the times it sets eofbit even on real read failure
        stream.peek();

        offset += reads;
        count -= reads;
    }

    return offset;
}

#define BIGGER_BUFFER_SIZE 200000000

int main(int argc, char* argv[])
{
    ifstream stream;
    stream.exceptions(ifstream::badbit | ifstream::failbit);
    stream.open("<big file on usb stick>", ios::binary);

    char *buffer = new char[BIGGER_BUFFER_SIZE];

    streamsize reads = readeof(stream, buffer, BIGGER_BUFFER_SIZE);

    if (stream.eof())
        cout << "eof" << endl << flush;

    delete buffer;

    return 0;
}

Итог: в linux поведение более последовательное и значимое.С включенными исключениями при реальных сбоях чтения он выдаст sgetn().Напротив, в большинстве случаев Windows будет рассматривать ошибки чтения как EOF.

...