Подтвердить что ты не робот

Почему бит бит установлен, когда eof найден при чтении?

Я читал, что <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) байты в потоке, прежде чем делать это?

4b9b3361

Ответ 1

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

#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 в большинстве случаев.

Ответ 2

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

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

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

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

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

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

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

EDIT. Важной деталью, которую я забыл упомянуть, является то, что eofbit можно установить без запуска failbit. Например, предположим, что у меня есть текстовый файл, содержащий текст

137

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

ifstream input("myfile.txt");

int value;
input >> value;

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

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

Ответ 3

Использование базового буфера прямо похоже на трюк:

char buffer[10];
streamsize num_read = stream.rdbuf()->sgetn(buffer, sizeof(buffer));