Как сериализовать данные структуры в C ++? - PullRequest
0 голосов
/ 24 февраля 2020

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

struct AMG_ANGLES {
    float yaw;
    float pitch;
    float roll;
};

char b[sizeof(struct AMG_ANGLES)];

char* encode(struct AMG_ANGLES *a)
{

    std::memcpy(b, &a, sizeof(struct AMG_ANGLES));
    return b;
}

void decode(char* data)
{
 // check endianess   
    AMG_ANGLES *tmp; //Re-make the struct
    std::memcpy(&tmp, data, sizeof(tmp));
}

Это правильно? Кто-нибудь может дать альтернативный дизайн? Я не получил обратный вызов, поэтому я просто пытаюсь узнать, что я мог улучшить.

Ответы [ 4 ]

6 голосов
/ 24 февраля 2020

Это правильно?

Скорее всего, нет.

Цель сериализации - преобразовать данные в форму, полностью независимую от платформы - например, не полагайтесь на такие вещи, как endianess, или если float - это IEEE 754 или что-то совсем другое. Для этого требуется:

a) строгое согласие с предполагаемым форматом - например, если это какой-то текст (XML, JSON, CSV, ...) или если это «необработанный двоичный файл» с явными определениями значения каждого отдельного байта (например, как, может быть, «байт 1 - это всегда младшие 8 битов значимого»).

b) правильное преобразование в какой-либо предполагаемый формат (например, может быть, как обеспечение того, что байт 1 всегда младшие 8 битов значения и независимо от различий между всеми платформами)

Однако; по крайней мере, технически возможно, что код не должен быть переносимым, а спецификация («соглашение о предполагаемом формате») совпадает с тем, что вы получили в результате для единственной платформы, для которой разработан код; и поэтому, по крайней мере, технически возможно, что код правильный.

0 голосов
/ 25 февраля 2020

может ли кто-нибудь дать альтернативный дизайн в C?

"Стандартным" способом было бы использовать printf и scanf для создания ascii-представления данных:

#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <assert.h>
#include <float.h>

struct AMG_ANGLES {
    float yaw;
    float pitch;
    float roll;
};

// declare a buffer at least this long to be sure encode works properly
#define AMG_ANGLES_BUFSIZE  ( \
    3 * ( /* 3 floats */ \
         2 + /* digit and dot */ \
         FLT_DECIMAL_DIG - 1 + /* digits after dot */ \
         4 /* the 'e±dd' part */ \
    ) \
    + 2 /* spaces */ \
    + 1 /* zero terminating character */ \
)

int encode(char *dest, size_t destsize, const struct AMG_ANGLES *a) {
    return snprintf(dest, destsize, "%.*e %.*e %.*e", 
         FLT_DECIMAL_DIG - 1, a->yaw, 
         FLT_DECIMAL_DIG - 1, a->pitch, 
         FLT_DECIMAL_DIG - 1, a->roll);
    // my pedantic self wants to add `assert(snprintf_ret < AMG_ANGLES_BUFSIZE);`
}

int decode(struct AMG_ANGLES *dest, const char *data) {
    return sscanf(data, "%e %e %e", &dest->yaw, &dest->pitch, &dest->roll) == 3 ? 0 : -1;
}

int main() {
   char buf[AMG_ANGLES_BUFSIZE];
   const struct AMG_ANGLES a = { FLT_MIN, FLT_MAX, FLT_MIN };
   encode(buf, sizeof(buf), &a);
   struct AMG_ANGLES b;
   const int decoderet = decode(&b, buf);
   assert(decoderet == 0);
   assert(b.yaw == FLT_MIN);
   assert(b.pitch == FLT_MAX);
   assert(b.roll == FLT_MIN);
}

Однако во встроенном голом металле я стараюсь не использовать scanf - это большая функция с некоторыми зависимостями. Так что лучше самому позвонить strtof, но для этого нужно подумать:

int decode2(struct AMG_ANGLES *dest, const char *data) {
    errno = 0;

    char *endptr = NULL;
    dest->yaw = strtof(data, &endptr);
    if (errno != 0 || endptr == data) return -1;
    if (*endptr != ' ') return -1;

    data = endptr + 1;
    dest->pitch = strtof(data, &endptr);
    if (errno != 0 || endptr == data) return -1;
    if (*endptr != ' ') return -1;

    data = endptr + 1;
    dest->roll = strtof(data, &endptr);
    if (errno != 0 || endptr == data) return -1;
    if (*endptr != '\0') return -1;

    return 0;
}

или с удаленным дублированием кода:

int decode2(struct AMG_ANGLES *dest, const char *data) {
    // array of pointers to floats to fill
    float * const dests[] = { &dest->yaw, &dest->pitch, &dest->roll };
    const size_t dests_cnt = sizeof(dests)/sizeof(*dests);
    errno = 0;
    for (int i = 0; i < dests_cnt; ++i) {
        char *endptr = NULL;
        *dests[i] = strtof(data, &endptr);
        if (errno != 0 || endptr == data) return -1;
        // space separates numbers, last number is followed by zero
        const char should_be_char = i != dests_cnt - 1 ? ' ' : '\0';
        if (*endptr != should_be_char) return -1;
        data = endptr + 1;
    }
    return 0;
}

Мне нужно было использовать гугл и перечитать Ответы chux, чтобы правильно вспомнить, как использовать FLT_DECIMAL_DIG в printf для печати поплавков, скорее всего потому, что я редко работал с поплавками.

0 голосов
/ 24 февраля 2020

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

Некоторые мои мысли:

  1. Ваш код зависит от аппаратного обеспечения, на котором работает программа из-за выравнивания и порядка байтов . Таким образом, сериализованные данные не переносимы и не зависят от компилятора.

  2. char* encode(struct AMG_ANGLES *a) функция возвращает char*, возможно, она утечка. Чтобы предотвратить проблему, позвольте std::unique_ptr<T> определить время ее существования или обернуть ее классом. Но как-нибудь избавьтесь от указателей.

  3. Шаблонизируйте ваши операции сериализации / десериализации. В противном случае вы могли бы написать те же функции для других типов.

    template<typename T>
    char* encode( T* a ) // I leave signature as is, just to demonstrate
    {
         std::memcpy( b , &a , sizeof(T) );
         return b;
    }
    
  4. Если формат зависит от вас, лучше предпочесть удобочитаемые человеком, а не двоичное архивирование, такое как JSON, XML
0 голосов
/ 24 февраля 2020

Лучше создать классы вроде std :: stringstream .. std :: stringstream не годится для сохранения двоичных данных, но работает так, как вы хотите. поэтому я мог бы привести пример, который работает с std :: stringstream ..

Этот код реализован только для сериализации, но он также добавляет код для десериализации.

// C++11
template < typename T, typename decltype(std::declval<T>().to_string())* = nullptr>
    std::ostream& operator<< (std::ostream& stream, T&& val)
{
    auto str = val.to_string();
    std::operator <<(stream, str);
    return stream;
}

struct AMG_ANGLES {
    float yaw;
    float pitch;
    float roll;
    std::string to_string() const
    {
        std::stringstream stream;
        stream << yaw << pitch << roll;
        return stream.str();
    }
};

void Test()
{
    std::stringstream stream;
    stream << 3 << "Hello world" << AMG_ANGLES{1.f, 2.f, 3.f };
}

...