Почему чтение файла в std :: string имеет неправильную длину Windows? - PullRequest
3 голосов
/ 04 апреля 2020

Я пробовал различные идиоматические c C++ решения для чтения файла в std::string на Windows, и это доставляло мне кучу головных болей, так как длина std::string была постоянно неправильной (например, много меньше исходного файла). Все эти же коды прекрасно работали на Linux.

Следующие функции работают некорректно на Windows:

#include <boost/filesystem.hpp>
#include <iostream>
#include <sstream>

using namespace boost::filesystem;
using namespace boost::iostreams;

std::string read_string_from_file_using_streams(const path &file_path) {
    return read_string_from_file_using_streams(file_path.parent_path(), file_path.filename().string());
}

std::string read_string_from_file_using_streams(const path &parent_directory, const std::string &file_name) {
    const auto original_file_path =
            parent_directory.string() + (char) path::preferred_separator + file_name;
    const std::ifstream input_stream(original_file_path);

    if (input_stream.fail()) {
        throw std::runtime_error("File is not readable");
    }

    std::stringstream buffer;
    buffer << input_stream.rdbuf(); // Does not read the whole file on Windows!

    return buffer.str();
}

std::string read_string_from_file_with_string_pre_allocation(const std::string& path) {
    std::ifstream t(path);
    std::string str;

    t.seekg(0, std::ios::end);
    str.reserve(t.tellg()); // Reserves the correct length
    t.seekg(0, std::ios::beg);

    str.assign(std::istreambuf_iterator<char>(t), // Does not read the whole file on Windows!
        std::istreambuf_iterator<char>());

    return str;
}

std::string read_string_from_file_using_if_stream(const std::string& path) {
    std::ifstream file(path);
    return std::string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()); // Does not read the whole file on Windows!
}

Наконец, следующий стандартный пример кода C работает правильно:

std::string read_using_c_standard_library(const std::string& path) {
    auto f = fopen(path.c_str(), "rb");
    fseek(f, 0, SEEK_END);
    long fsize = ftell(f);
    fseek(f, 0, SEEK_SET);

    auto string = (char *) malloc(fsize + 1);
    fread(string, 1, fsize, f); // This reads the whole file on Windows, finally!
    fclose(f);

    string[fsize] = 0;

    std::string s(string, fsize);
    free(string);
    return s;
}

Я не понимаю, почему это происходит. Что не так с подходами C++? Мой пример файла 516 KB, а функции с ошибками читают только, например 912 bytes.

Ответы [ 2 ]

3 голосов
/ 04 апреля 2020

Видите ли вы разницу?

const std::ifstream input_stream(original_file_path);

auto f = fopen(path.c_str(), "rb");

Во втором случае файл открывается в двоичном режиме, в первом - нет.

При этом код должен все еще прочитайте весь файл, но он изменит окончание строки, поэтому размер полученной строки может оказаться не таким, как вы ожидаете.

1 голос
/ 04 апреля 2020

В Windows окончания строк текстовых файлов обычно \r\n, в Linux они просто \n.

Открытие файла в текстовом режиме приводит к окончанию строки \r\n для преобразования в \n, чтобы они были \n на обеих платформах.

Однако это следует делать только для текстовых файлов, а не для двоичных файлов, поскольку в противном случае все последовательности байтов \r\n будут преобразовать в \n, что приведет к повреждению данных и уменьшению размера файла.

Кроме того, в текстовом режиме на Windows байт со значением 0x1A интерпретируется как конец файла (EOF). ), даже если фактическая длина файла больше. Однако в двоичном режиме байт с этим значением не интерпретируется как имеющий какое-либо специальное значение.

Строка

std::ifstream file(path);

откроет файл в текстовом режиме, тогда как строка

auto f = fopen(path.c_str(), "rb");

откроет файл в двоичном режиме.

Это, вероятно, причина, по которой вы получаете файлы различной длины.

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