Двоичный генератор тестового файла с контрольной суммой


Читая этот вопрос, я подумал, что было бы полезно создать тестовые файлы в соответствии с форматом файла спецификации. Напомним, что формат файла выглядит следующим образом:

Формат файла

Обратите внимание, что все числовые значения находятся в прямой порядок байтов формата и контрольная сумма рассчитывается по всем файла за исключением в поле контрольной суммы. $$ \начать{массив}{Л|С|Л} \текст{имя} & \текст{длина в байтах} & \текст{описание} \\ \hline на \текст{размер} & 4 & \текст{общее количество байтов в файл} \\ \текст{граф личности} & 2 & \Текст{число человеко записи в файл} \\ \текст{человек(s)} & \текст{зависит} & \текст{последовательность людей записи} \\ \текст{заполненые нулями} & 0..3 & \текст{отступы, чтобы сделать размер файла кратен 4} \\ \текст{Контрольная} & 4 & \текст{контрольная сумма файла как 32-разрядные значения без знака} \конец{массив} $$

Человек формат записи

$$ \начать{массив}{Л|С|Л} \текст{имя} & \текст{длина в байтах} & \текст{описание} \\ \hline на \текст{первый размер имя} & 4 & \текст{число символов в имени} \\ \текст{имя} & \текст{зависит} & \текст{текст имя (без завершающего нулевого символа типа char)} \\ \текст{последний размер название} & 4 & \текст{число символов в фамилии} \\ \текст{фамилия} & \текст{зависит} & \текст{текст фамилия (без завершающего нулевого символа типа char)} \\ \текст{флаги} & 1 & \текст{флаг битов: бит 0 = возраст поля, бит 1 = высота поля} \\ \текст{возраст}^* & 1 & \текст{возраст в годах. Необязательное поле} \\ \текст{высота}^* & 1 & \текст{высота в дюймах. Необязательное поле} \\ \конец{массив} $$ Звездочкой в поле " имя " указывает, что поле является обязательным.

Вопросы

Код в основном простой C++11, но есть несколько особенностей, хотелось бы конкретных замечаний по. Первый-это использование с++20 std::endian. Есть ли лучший способ сделать это? Второй-это использование static_assert убедиться в том, что итератор разыменовывает к Person класс. Будет enable_if быть лучший способ справиться с этим? Если да, то что именно синтаксис будет? Наконец-то, я изначально хотел сделать эту работу за один проход, но расчет контрольной суммы был значительно чище за счет резервного копирования в файл. Есть ли элегантный способ сделать то же самое вычисление контрольной суммы без резервного копирования?

maketest.cpp

#include <iostream>
#include <fstream>
#include <string>
#include <iomanip>
#include <array>
#include <sstream>
#include <numeric>

// in C++20 we will have std::endian, but until then, we roll our own
#if 0
#include <type_traits>
#else
namespace std {
enum class endian
{
#ifdef _WIN32
    little = 0,
    big    = 1,
    native = little
#else
    little = __ORDER_LITTLE_ENDIAN__,
    big    = __ORDER_BIG_ENDIAN__,
    native = __BYTE_ORDER__
#endif
};
}
#endif

static_assert(std::endian::native == std::endian::little, "Error: code is only intended for little-endian machines.\n");

class Person {
public:
    Person(const std::string &firstname, const std::string &surname, int age=0, int height=0) :
        firstname{firstname},
        surname{surname},
        age{age},
        height{height}
    {}

    friend std::ostream &operator<<(std::ostream &out, const Person &p) {
        return out << "{ { firstname : " << p.firstname
                    << " }, { surname : " << p.surname
                    << " }, { age : " << p.age
                    << " }, { height : " << p.height
                    << " }";
    }
    std::size_t length() const {
        return 9 + firstname.size() + surname.size() + (age ? 1 : 0) + (height ? 1 : 0);
    }
    std::size_t write(std::ostream &out) const {
        auto begin{out.tellp()};
        std::uint8_t flags{static_cast<std::uint8_t>((height ? 2 : 0) | (age ? 1 : 0))};
        std::uint32_t firstlen{static_cast<uint32_t>(firstname.size())};
        std::uint32_t lastlen{static_cast<uint32_t>(surname.size())};
        out.write(reinterpret_cast<const char *>(&firstlen), sizeof firstlen);
        out << firstname;
        out.write(reinterpret_cast<const char *>(&lastlen), sizeof lastlen);
        out << surname;
        out.write(reinterpret_cast<const char *>(&flags), sizeof flags);
        if (age) {
            out.write(reinterpret_cast<const char *>(&age), 1);
        }
        if (height) {
            out.write(reinterpret_cast<const char *>(&height), 1);
        }
        return out.tellp() - begin;;
    }

private:
    std::string firstname;
    std::string surname;
    int age;
    int height;
};


template<class InputIt>
std::ostream &write(InputIt first, InputIt last, std::iostream &out)
{
    static_assert(std::is_convertible<decltype(*first), const Person&>::value, "Error: iterator to write() must dereference to Person class");
    std::uint32_t total_len{10 + std::accumulate(first, last, 0u, [](unsigned t, const Person &p){ return t + p.length(); })};
    std::size_t pad_len{4 - (total_len % 4)};
    if (pad_len == 4) {
        pad_len = 0;
    }
    total_len += pad_len;
    out.write(reinterpret_cast<const char *>(&total_len), sizeof total_len);
    std::uint16_t count{static_cast<uint16_t>(last - first)};
    out.write(reinterpret_cast<const char *>(&count), sizeof count);
    for (auto &p{first}; p != last; ++p) {
        p->write(out);
    }
    unsigned long pad{0};
    out.write(reinterpret_cast<const char *>(&pad), pad_len);
    out.seekg(0);
    std::uint32_t cksum{0};
    for (std::size_t i{total_len / 4 - 1}; i; --i) {
        std::uint32_t piece;
        out.read(reinterpret_cast<char *>(&piece), sizeof piece);
        cksum += piece;
    }
    out.write(reinterpret_cast<const char *>(&cksum), sizeof cksum);
    return out;
}

int main(int argc, char *argv[]) {
    const std::array<Person, 6> people {{
        { "Abigail", "Adams", 6, 0 },
        { "Bob", "Barker", 40, 72 },
        { "Charles", "Cook", 0, 55 },
        { "Deborah", "Dawson", 0, 0 },
        { "Edward", "Electron", 45, 70 },
        { "Freddie", "Freeloader", 0, 0 },
    }};
    if (argc != 2) {
        std::cout << "Usage: maketest filename\n";
        return 0;
    }
    std::fstream out{argv[1], std::ios_base::in|std::ios_base::out|std::ios_base::trunc};
    if (!out) {
        std::cerr << "Error opening file \"" << argv[1] << "\"\n";
        return 1;
    }
    write(people.begin(), people.end(), out);
}

Результат

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

make maketest && maketest test.in && xxd test.in

Результирующий файл, как показано xxd это:

00000000: 9400 0000 0600 0700 0000 4162 6967 6169  ..........Abigai
00000010: 6c05 0000 0041 6461 6d73 0106 0300 0000  l....Adams......
00000020: 426f 6206 0000 0042 6172 6b65 7203 2848  Bob....Barker.(H
00000030: 0700 0000 4368 6172 6c65 7304 0000 0043  ....Charles....C
00000040: 6f6f 6b02 3707 0000 0044 6562 6f72 6168  ook.7....Deborah
00000050: 0600 0000 4461 7773 6f6e 0006 0000 0045  ....Dawson.....E
00000060: 6477 6172 6408 0000 0045 6c65 6374 726f  dward....Electro
00000070: 6e03 2d46 0700 0000 4672 6564 6469 650a  n.-F....Freddie.
00000080: 0000 0046 7265 656c 6f61 6465 7200 0000  ...Freeloader...
00000090: 15b1 8582                                ....

Обратите внимание, что эта контрольная сумма никак не совпадает с рассчитанной по коду в связанном вопросе. Он рассчитывает 0x8285b015 вместо того, что я считаю, что ошибка вызвана тем, что код отказа счета для Интер-байт нести.



294
7
задан 20 февраля 2018 в 01:02 Источник Поделиться
Комментарии
1 ответ

Утверждение, кажется, немного свихнулся, должно быть:

static_assert(std::is_convertible<typename std::iterator_traits<InputIterator>::reference, const Person&>::value, "Error: iterator to write() must dereference to Person class");

Почему? В [ввод.итераторы], N3797 (стр. 832), таблица говорит, что *it должны быть конвертируемые в T. Я слишком устал, чтобы вытащить правила преобразования из верхней части моей головы, но я верю, что гарантирует, что хотя бы он будет отбрасывать неправильные и не нарваться на один из тех, подвешенные миров. Это будет большой вопрос юристу языка.

Когда использовать enable_if (например, SFINAE)?

Как говорит название, std::enable_if будут использованы только в SFINAE контексте. Если инстанцирование шаблона не проходит включить проверить, и не имеет ничего, чтобы вернуться в, это будет просто более загадочным static_assert. SFINAE, как правило, сложнее получить права, и, как правило, имеют небольшие ошибки, которые требуют очень глубокого понимания шаблонов, чтобы решить.

2
ответ дан 21 февраля 2018 в 01:02 Источник Поделиться