Правильное чтение бинарного файла в вектор чисел
Как максимально правильно (без UB, без предположений о размере типов, без предположения о размере байта) заполнить вектор unsigned int-ов из бинарного файла, размер которого кратен 4, порядок байтов little endian?
Проблема с reinterpret_cast -- размер unsigned int может быть 8 байтов, тогда, насколько я понимаю, read будет читать файл кусочками по 8 байтов, а надо по 4 всё равно (т.е. для 12-байтового файла должен быть размер вектора 3, а не непонятно что). Правда ли это?
Проблемы с 1) читаем массив char*; 2) ходим с шагом 4 и суммируем байты со сдвигом -- надо в 2 раза больше памяти; char может быть знаковым, тогда битовые операции могут выдавать отрицательные значения. Можно ли как-то переиспользовать память буфера char*? Можно ли без UB reinterpret_cast его как unsigned char или сразу читать с беззнаковым типом?
Ещё видел варианты с std::istream_iterator в этом ответе. Выглядит красиво, но не уверен, что это правильно, из-за использования >>.
Ответы (2 шт):
Просто считайте четыре байта из файла и битовыми операциями сконструируйте из них необходимое вам значение:
#include <climits>
#include <cstdint>
static_assert(CHAR_BIT == 8); //На всякий случай.
unsigned char buf[4];
fin.read(reinterpret_cast<char*>(&buf[0]), sizeof(buf));
std::uint_least32_t uint32_value =
(static_cast<std::uint_least32_t>(buf[0]) << 0) +
(static_cast<std::uint_least32_t>(buf[1]) << 8) +
(static_cast<std::uint_least32_t>(buf[2]) << 16) +
(static_cast<std::uint_least32_t>(buf[3]) << 24);
Или считываем весь файл разом, без доп. буфера, затем обрабатываем байты на месте:
#include <vector>
#include <cstdint>
#include <climits>
std::size_t vect_size = ...;
std::vector<std::uint32_t> vect(vect_size, 0);
fin.read(reinterpret_cast<char*>(&vect[0]), vect.size() * sizeof(vect[0]));
if (sizeof(vect[0]) != 1)
for (std::size_t i = 0; i < vect.size(); ++i)
{
auto val = vect[i];
vect[i] = 0;
for (std::size_t byte = 0; byte < sizeof(val); ++byte)
vect[i] |= static_cast<std::uint32_t>( *(reinterpret_cast<unsigned char*>(&val) + byte) ) << CHAR_BIT * byte;
}
Способ №3, учитывающий произвольное количество бит в байте, а также возможное наличие padding bits в типе.
#include <limits>
#include <array>
template <typename T>
constexpr int uchars_in_int()
{
static_assert(std::numeric_limits<T>::is_integer);
//Определяем количество бит в типе T, учавствующих в value representation
constexpr int digits_in_int =
std::numeric_limits<T>::digits + std::numeric_limits<T>::is_signed;
//Определяем количество объектов типа unsigned char, необходимых для
//представления одного объекта типа T
constexpr int res =
digits_in_int / std::numeric_limits<unsigned char>::digits +
(digits_in_int % std::numeric_limits<unsigned char>::digits > 0);
return res;
}
template <typename T>
std::array<unsigned char, uchars_in_int<T>()> int_to_uchars(T value)
{
static_assert(std::numeric_limits<T>::is_integer);
std::array<unsigned char, uchars_in_int<T>()> buf;
if (buf.size() == 1)
buf[0] = value;
else
for (unsigned i = 0; i < buf.size(); ++i)
{
buf[i] = value & static_cast<unsigned char>(-1);
value >>= std::numeric_limits<unsigned char>::digits;
}
return buf;
}
template <typename T>
T uchars_to_int(std::array<unsigned char, uchars_in_int<T>()> buf)
{
static_assert(std::numeric_limits<T>::is_integer);
if (buf.size() == 1)
return buf[0];
T value = 0;
for (unsigned i = 0; i < buf.size(); ++i)
value |= static_cast<T>(buf[i]) << (std::numeric_limits<unsigned char>::digits * i);
return value;
}
В файл пишем так:
some_int_type value = ...;
auto buf = int_to_uchars(value);
fout.write(reinterpret_cast<char*>(&buf[0]), buf.size());
Читаем из файла так:
std::array<unsigned char, uchars_in_int<some_int_type>()> buf;
fin.read(reinterpret_cast<char*>(&buf[0]), buf.size());
auto value = uchars_to_int<some_int_type>(buf);
Для повышения производительности заводим в памяти буфер нужного размера, пишем байты в буфер, затем буфер в файл / считываем файл в буфер, восстанавливаем инты из буфера.
Хотелось бы увидеть пояснение от поставившего минус. Если я в чём не прав или такой код как у меня пишут только в первом классе, вы не стесняйтесь, выскажетесь :) Мне действительно интересно, как правильно сериализовать инты в бинарный файл максимально кроссплатформенно.
std::vector<std::uint32_t> vec(size, 0);
fin.read(reinterpret_cast<char*>(&vec[0]), vec.size() * sizeof(vec[0]));