LINUX.ORG.RU

Сишечка - передача массива структур с полями пользовательского типа

 , ,


0

4

Вопрос меня тревожит не по граблям ли я хожу и как это вобще лучше сделать? Допустим имеется вот такой массив:

typedef enum {ONE, TWO, THREE} order_t;

typedef struct {
    ordet_t order;
    uint8_t* name;
    uint16_t lenght;
} data_t;

data_t arr[];

который необходимо передавать на другое устройство. И что бы избежать всяких возможных проблем при передачи есть мысль упаковать структуру (для сокращения данных для передачи), а при передаче передать указатель на массив приведя его к uint8_t

void data_trasfer((uint8_t*)arr);

Ну и соответсвующая сторона соответсвенно будет обратно кастовать к data_t. Такая своеобразная сериализация данных.



Последнее исправление: pavel_l (всего исправлений: 1)

при сериализации структура полностью разрушится. и все указатели исчезнут.
при десериализации ты заново создаешь и наполняешь структуру и указатели «появятся» сами собой.

pfg ★★★★★
()

не по граблям ли я хожу

Ходишь. name - указатель на строку, получишь адрес вместо текста. Это во-первых. data_trasfer не знает длину arr. Это во-вторых.

Возьми петон и не мучай себя сишкой. Сделаешь эту задачу за один вечер.

ox55ff ★★★★★
()

Так не надо делать. Нужна сериализация - разработай формат для передачи данных и напиши сериализатор и десериализатор для этого формата.

vbr ★★★★★
()

Всё правильно, просто приведи указатель к (uint8_t*) и отправляй как удобно размер байт sizeof(data_t). Единственное, следи за порядком байт и выравниванием, добавь аттрибуты packed.

Не слушай про protobuf и тд, это не требуется в большинстве случаев

SL_RU ★★★★
()

указатель на имя - просто адрес в памяти твоей машины, его передавать смысла нет. Чтобы такое передать надо пакетом передавать сначала order, length, потом саму строку(если length это ее длина)

на приеме принять order, lenght, алокировать буфер на эту длину, принять строку в буфер, поставить в поле name адрес буфера.

поскольку массив - это надо повторить для каждого элемента, перед передачей всего массива передать его длину.

если негарантированная передача - сделать crc сумму на data_t. при передаче считать сумму и передавать после передачи data_t

при приеме тоже считать crc сумму, если не совпадает - дуть к красный свисток и вызывать мастера.

все протоколы будут делать примерно то же самое.

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

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

указатель на имя - просто адрес в памяти твоей машины, его передавать смысла нет

Мне кажется, что если автору такие вещи не очевидны, то ему не стоит писать никакой рабочий код на Си пока что, а только учиться.

А в тегах stm32... Ужасно думать где он там ещё наделал багов и кто будет в итоге пользоваться его изделием.

firkax ★★★★★
()

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

если задача просто передать данные - pragma pack(1) упакует структуру. но указатель name будет содержать мусор на другом девайсе. поэтому надо отправлять длину данных name и после упакованной структуры-хэдера отдельно уже сами данные, чтобы на другой стороне прочитать структуру, получить длину и считать из принятого буфера нужное количество байтов данных name. как-то так.

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

Iron_Bug ★★★★★
()
Ответ на: комментарий от firkax

нуу... в некоторых сложных проектах я его видела. не могу сказать, что эта трахома меня возбужает, но она работает и если структур данных много, они фиксированы и не часто меняются (чтобы не генерить всё каждый раз), то это оправданно. он и был создан Гуглом для крупных проектов, где сериализуется куча данных. они его делали под свои задачи, а потом генератор расшарили, как опенсорц. но подарочки от Гугла надо трогать очень длинной палкой, из-за угла - во избежание. так что лучше всякое такое монстрозное на каждый чих в софтины не тащить. и зависимостей будет меньше, и жить будет проще.

Iron_Bug ★★★★★
()

зачем хранить указатель на строку когда можно саму строку хранить в структуре? тогда и размер будет всегда известен и передать можно как есть (только нужно выравнивание учесть/убрать). и на другой стороне можно обратно скастовать: data_t* data = (data_t*)buff

Rost ★★★★★
()
Последнее исправление: Rost (всего исправлений: 1)
Ответ на: комментарий от Iron_Bug

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

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

vbr ★★★★★
()
Ответ на: комментарий от alysnix

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

Iron_Bug ★★★★★
()

обычна длина идет перед полем переменной длины, это позволяет выделять буфер на лету.

ну и можно сразу задать структуру для строк

typedef struct {
  uint16_t sz,
  uint8_t* str
} t_str;

t_str a = { .sz=5, .str="12345" };



printf("%.*s\n", a.sz, a.str);

а так по хорошему все это нужно упаковывать в какой-то канальный протокол с символами синхронизации, бит/байт стаффингом, crc и тд

для понимания можно посмотреть что-то типа

https://microsin.net/adminstuff/others/xmodem-protocol-overview.html

https://ru.wikipedia.org/wiki/Modbus

zudwa
()
Последнее исправление: zudwa (всего исправлений: 2)
Ответ на: комментарий от beastie

protobuf нужен в основном если обменивающиеся системы написаны на разных языках программирования, для максимальной переносимости. Так же как и остальные msgpack, bson, avro etc и чисто текстовые json, xml

В случае, когда обмен идет между С системами смысла в этом protobuf, пмсм, немного.

zudwa
()
Последнее исправление: zudwa (всего исправлений: 1)

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

pavel_l
() автор топика
Ответ на: комментарий от anonymous

Их (полноценые) реализации влезут в микроконтроллер?

А в чём проблема? Сдаётся мне, что тут просто не понимают, что это такое и с чем его едят.

И то и другое суть wire-protocol – то самое, что тут изобретают – как байтики в провод укладывать и от туда доставать.

Разница лишь в том, что там есть кодо-генерация, а тут ручками велосипеды пишут.

Результирующий объём кода, что ручками, что автоматом примерно одинаковый.

PS: берём пример и смотрим сами.

syntax = "proto3";

package example;

message Data {
  enum Order {
    ONE = 0;
    TWO = 1;
    THREE = 2;
  }
  Order order = 1;
  string name = 2;
}
protoc --c_out=. example.proto

На выходе 2 файла example.pb-c.c и example.pb-c.h с полной имплементацией маршалера/демаршалера чуть больше чем на 100 строк (включая комментарии и пустые строки). ¯\_(ツ)_/¯

PPS: всякие nanopb и в меньше уложатся.

beastie ★★★★★
()
Последнее исправление: beastie (всего исправлений: 6)

Сильно удобного решения для Си в случае уже готовой структуры нет, но можно:
1. Задефайнить описатель структуры вручную в виде массива из типов и смещений полей
2. Реализовать (де)сериализацию через обход этого массива.
Тогда тебе не придётся переделывать саму структуру, но придётся определить способ (де)сереализации каждого поля.
Можно попытаться применить какой-нибудь функциональный макрос для определения полей, чтобы изежать излишней писанины, если таких структур много.
Например:

typedef uint8_t* uint8_t_ptr;
#define DATA_STRUCT(f,s) \
f(s, uint32_t, order) \
f(s, uint8_t_ptr, name) \
f(s, uint16_t,length) \

#define DECLARE_STRUCT(s, type, name) type name;
#define DECLARE_DESC(s, type, name) {type##_e, offsetof(s,name)}
typedef struct {
DATA_STRUCT(data_t, DECLARE_STRUCT)
} data_t;
typedef enum {
uint32_t_e,
uint8_t_ptr_e,
uint16_t_e
} desctype_t;

typedef struct {
desctype_t t;
size_t off;
} desc_t;

desc_t data_desc[] = 
{
DATA_STRUCT(data_t, DECLARE_DESC)
}


После этого написать функцию, которая по массиву data_desc будет разбирать или собирать эту структуру.
Функциональный макрос позволит избежать записи структуры несколько раз, но всё это не очень хорошо читается. Код примера не проверял, могут быть ошибки
Конечно если структуру можно менять - можно сразу задефайнить её так, чтобы она на была стабильной (но если нужна совместимость данных с big endian всё равно придётся свапать данные)

mittorn ★★★★★
()
Последнее исправление: mittorn (всего исправлений: 4)

Да, кстати, на c++17 можно через некоторые костыли сделать автоматический обход структуры по полям:
https://gitlab.com/mittorn/xrBinder/-/blob/master/src/struct_utl.h?ref_type=h...
Так же есть куча всяких модных библиотек для рефлексии и сериализации вроде pfr, но практически все они разваливаются как только в структуре встречается массив. Мой вариант пускай медленно (по времени компиляции), но хотя бы работает в этом случае

mittorn ★★★★★
()
Ответ на: комментарий от mittorn

Нано-дрочество? И тут ты так не слабо приврал. Хоть бы сам на цифры глянул.

Но ладно. Раз тут так боятся protobuf (а его преимущество, что это де-факто стандарт), возьмите что-нибудь другое. Всё лучше, чем велосипед переизобратать.

beastie ★★★★★
()
Последнее исправление: beastie (всего исправлений: 1)

uint8_t* name;

Сработает то что ты хочешь, только в случае если name не будет указателем, а будет массивом, который является частью тела структуры, типа

uint8_t name[MAX_NAME];

Такая своеобразная сериализация данных.

Это не сериализация, при сериализации сериализируют =) то есть досконально проверяют каждое поле данных на предмет ожидаемого.

Ты можешь высчитывать поля структуры и записывать их явно как байты в строку, таким образом ты сформируешь формат данных, где последовательность имеет определённый порядок и определённый размер. Эту строку можно слать куда угодно. На месте приёма должена быть получена строка и её размер, количество «полей» внутри известно, у тебя же свой формат и размеры тоже, если входящий размер меньше или больше то ошибка, а далее считываеются байты, 1 сюда, 4 туда из них формуруется структура, а байтики прикодятся к типу полей структуры, по значению и всё. Иными словами, явно «распаковал» структуру (или массив структур) в один байтовый массив, содержимое и структура которого за ранее изветны, отправил, получил и разобрал массив обратно в структуры на принимающем устройстве, естественно проверяя всё. Так как у тебя ситуация простая, то нет смысла прибегать к чему-то готовому типа протобуфа и прочего. А можно и без сериализации, если это в коробочке Atmega8 и STM32 в друг друга по UART данными пуляются и сидят в одной пластиковой коробочке например, только размер и цельность данных проверять и всё.

Вот ещё можно почитать, про дырки

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 2)
Ответ на: комментарий от beastie

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

zudwa
()
Последнее исправление: zudwa (всего исправлений: 1)

Я один тут в упор не могу понять, в чем вообще смысл вот этого

при передачи есть мысль упаковать структуру (для сокращения данных для передачи), а при передаче передать указатель на массив приведя его к uint8_t

Что значит упаковать структуру? Как можно на другое физическое устройство передать указатель, не передав сами данные? Чтобы что. Данные на том устройстве из астрала восстановятся?

James_Holden ★★★★★
()
Ответ на: комментарий от mittorn

Там сравнивается несравнимое. У того же json не одна имплементация.

И главный слон в комнате: мы ведь про сетевую передачу, не так ли?

Где размер данных и латенция канала перевешивает все съэкономленные нано-секунды при сериализации/десериализации.

И на удивление, protobuf тут выигрывает.

beastie ★★★★★
()
Ответ на: комментарий от James_Holden

Что значит упаковать структуру

#pragma pack

Как можно на другое физическое устройство передать указатель, не передав сами данные

Про передачу указателя на другое физическое ус-во и слова не было, указатель передается в функцию которая уже шлет данные «в провод» прототип которой сразу после приведенной цитаты.

pavel_l
() автор топика
Последнее исправление: pavel_l (всего исправлений: 1)
Ответ на: комментарий от beastie

Всё равно всё сильно зависит от количества таких структур, производительности процессора и ширины канала. Иногда экономия пары десятков байт не так заметна, как увеличение кода программы, усложнение процесса сборки, или же увеличение потребления в рантайме. Иногда может быть лучше и структуру с bitfield'ами завести. А в сжатых битстримах h264/h265 так вообще байтики вручную пакуют:
https://git.disroot.org/mittorn/vulkan-playground/src/branch/master/bitstream.h
https://git.disroot.org/mittorn/vulkan-playground/src/branch/master/vaapi_enc...

mittorn ★★★★★
()
Ответ на: комментарий от mittorn

Так он же будет отсылать массив по значению на другое устройство, а там принимать, как поток байт. А приведение к строке uint8_t это просто будет для передачи данных в функцию которая те самые байты и отправлять будет. Указатель то будет существовать только на этапе формирования данных и передачи их дальше на обработку, пофигу какой он там будет, он никуда не передаётся и значение его, кхм значения не имеет =)

Иными словами, он хочет тоже самое что и «записать массив структур в файл» и «прочитать массив структур из файла», просто вместо файла, некая сеть между некими устройствами.

Ну, я так понял.

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 1)
Ответ на: комментарий от LINUX-ORG-RU

Ну, если это MCU (а судя по тегам да) и там гарантированно будет прошита одна версия софта, то указатель будет указывать на тот же объект. Но если софт не одинаковый - он может уехать

mittorn ★★★★★
()
Ответ на: комментарий от mittorn

Ну, видимо я что-то не понимаю, я просто про пересылку данных например по UART между Atmega и STM32 например. Ну тоесть никакой связи кроме обмена байтами нет. Если эти устройства оба onchip имеются в виду и данные передаются через общую память или ещё какую жесть и это вот имеется в виду, ну тогда я значит вообще не про то писал :)

Ну и ладна тогда, гы

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

Да структура практически то, что будет отправляться, да стопкой, т.к. порядок имеет значение. Можно конечно слать и по одиночке предварительно отправляя какой-то опозновательный/управляющий символ. Но стопкой мне кажется проще.

pavel_l
() автор топика