Сериализовать структуру, содержащую гибкий массив

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

Это моя первая попытка, которая, несмотря на свою нефункциональность, хорошо объясняет мое намерение.

typedef struct {
  uint8_t x;
  uint8_t *y;
} A;

A a;
uint8_t Y[5] = {1, 2, 3, 4, 5};

void setup() {
  Serial.begin(115200);

  a.x = 12;
  a.y = Y;

  uint8_t * a_arr = (uint8_t *) &a;

  for (uint8_t i = 0; i < sizeof(A); i++) {
    Serial.print(a_arr[i]);
    Serial.print(" ");
  }
  Serial.println();
}

Очевидно, что это не работает, поскольку сериализуется сам указатель, а не содержимое массива. Таким образом, массив 1, 2, 3, 4, 5 заменяется адресом указателя в выходных данных. Результат здесь: 12 0 1.

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

typedef struct {
  uint8_t x;
  uint8_t *y;
} A;

A a;
uint8_t Y[5] = {1, 2, 3, 4, 5};

void setup() {
  Serial.begin(115200);

  a.x = 12;
  a.y = Y;

  uint8_t total_size = 6;
  uint8_t * a_arr = malloc(total_size);

  struct_2_buf(&a, total_size-1, a_arr);

  for (uint8_t i = 0; i < total_size; i++) {
    Serial.print(a_arr[i]);
    Serial.print(" ");
  }
  Serial.println();
}

void struct_2_buf(A * a, uint8_t y_size, uint8_t * buf){
  buf[0] = a->x;
  for (uint8_t i = 0; i < y_size; i++) {
    buf[i + 1] = a->y[i];
  }
}

Теперь вывод 12 1 2 3 4 5, как и ожидалось.

Это работает хорошо, но необходимость составлять массив из каждой части структуры меня немного смущает, так как я нашёл uint8_t * a_arr = (uint8_t *) &a; довольно удобным. Это хорошо абстрагируется от содержимого структуры. Кроме того, необходимость вычислять размер вместо использования sizeof(A) просто добавляет больше кода для управления.

Есть ли лучший или более оптимальный способ сделать это?

ДОПОЛНЕНИЕ: Моя цель — не вывести структуру, а сохранить массив буферов, содержащий мою структуру. В данном случае a_arr. Который я затем буду использовать для передачи через библиотеку RF.

, 👍0

Обсуждение

какая часть вашего поста посвящена проблеме с Arduino?, @jsotola

Согласен, это можно было бы разместить и в разделе обмена опытом по C-стеку. Однако в прошлый раз, когда я это делал, мне попадались ответы, совершенно не относящиеся к Arduino. Например, предлагались решения с использованием тяжёлых библиотек или ответы, не адаптированные для встраиваемого ПО. Мой код должен работать на оборудовании типа Arduino UNO, и именно в рамках Arduino мне нужно решение этой проблемы. Так что да, весь пост — это проблема Arduino., @Noel

Если вы готовы заранее выделить память на худший случай, вы также можете использовать объединение всех отправляемых типов «записей» (структур). Для отправки данных вы также добавляете массив байтов. Внутри объединения хранилище перекрывается, поэтому можно использовать только одну структуру за раз. (Это неудобно, поскольку C++ позволяет добавлять заполнение, но в большинстве случаев компилятор поддерживает поведение C, что позволяет этому работать. Кроме того, это может нарушать правила каламбуров типов.), @6v6gt


1 ответ


5

Ваш пример кода сериализует структуру данных в память. буфер, только чтобы потом отправить его через последовательный порт. Я предлагаю вам сделать это убрать буфер памяти и вместо этого просто отправлять отдельные элементы Через последовательный порт напрямую. Таким образом, вы избежите динамической памяти. выделение памяти (malloc(), которое в C++ должно быть new). Некоторые Arduino очень ограничены в оперативной памяти и избегают динамического выделения памяти (и потенциальная фрагментация, которая с этим связана) обычно очень желательно.

необходимость вычислять размер вместо того, чтобы использовать sizeof(A), просто добавляет больше кода для управления.

Размер массива в идеале должен храниться в структуре данных. сам.

Интересно, является ли это самым элегантным и оптимальным способом сделать это?

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

A a;
uint8_t Y[5] = {1, 2, 3, 4, 5};

void setup() {
    Serial.begin(115200);

    a.x = 12;
    a.y = Y;
    a.y_size = sizeof Y;
    Serial.println(a);  // Да, все так просто!
}

Чтобы A можно было распечатать, он должен быть производным от Printable, и вам нужно реализовать чисто виртуальную функцию printTo(), которая сообщает компилятор, как вывести его на любой объект Print (например, Serial):

struct A : Printable {
    uint8_t x;
    uint8_t y_size;
    uint8_t *y;
    size_t printTo(Print& p) const;
};

size_t A::printTo(Print& p) const {
    size_t byte_count = p.print(x);
    for (int i = 0; i < y_size; i++) {
        byte_count += p.print(' ');
        byte_count += p.print(y[i]);
    }
    return byte_count;
}
,

Гениальный и специфичный для Arduino ответ!, @the busybee

Спасибо. Здесь много полезной информации. Мне следовало бы указать, что моя цель — сериализовать данные, поместив их в буферный массив, где хранится моя структура для отправки с помощью библиотеки RF. А не для вывода данных через последовательный порт. Мой пример выводит их для теста, но в конечном итоге меня интересует именно a_arr. Тем не менее, ваш вклад дает ответы на несколько моих вопросов., @Noel

Ваша функция printTo каким-то образом отвечает на мой вопрос и может подтвердить, что мой второй код является структурно единственным имеющимся у нас вариантом, т. е. обрабатывает каждую часть структуры по отдельности, а не как единое целое., @Noel

@Noel: Этот метод применим не только к Serial: он работает со всем, что наследуется от Print (Wire, LiquidCrystal, WiFiClient и т. д.). Какую библиотеку RF вы используете? Какой API она предлагает для отправки вывода? Поддерживает ли она какой-либо метод print()?, @Edgar Bonet

Это библиотека RFM69 от lowpowerlab. Насколько мне известно, я не видел в ней метода печати. Она использует команду типа send(to_node, buffer, buffer_size)., @Noel