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)
Ответ на: комментарий от beastie

вопрос не в производительности, а в целостности передаваемых данных, поиске начала и конца.

protobuf этого не умеет. это просто инструмент маршалинга.

тс пишет, что хочет передавать name максимальнольной длиной 2^16

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

просто protobuf недостаточно.

ну и канал связи - это канал связи, он идеален только на коленке;)

zudwa
()

а при передаче передать указатель на массив приведя его к uint8_t

На отправляющей стороне у тебя должно быть.

NetWriteInt32(arr[i].order);
NetWriteString(arr[i].name); // отправляет байты до NUL

На принимающей

data_t *data = malloc(sizeof(data_t));

NetReadInt32(&data->order);
NetReadChars(&data->name);

uint16_t lenght; не нужен и сделай name char*

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

Не оптимальное решение:

  • Динамическое выделение памяти на mcu такое себе

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

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

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

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

А теперь про код:

typedef enum {ONE, TWO, THREE} order_t;

ONE, TWO и THREE — это не макросы. Не нужно их писать капсом. Капсом нормальные люди пишут только макросы.

ONE, TWO и THREE НЕ будет равны 1, 2 и 3 соответственно, потому что это не указано в enum.

enum будет занимать размер sizeof(int) (если не применяются специальные расширения компилятора), вместо 1 байта, если бы ты просто использовал char.

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

Нужно сделать вот так:

typedef struct {
    uint8_t* name;
    ordet_t order;
    uint16_t lenght;
} data_t;
чтобы структура занимала минимум байт.

uint8_t, uint16_t

Это что за хрень? В Си эти типы всегда называются unsigned char и unsigned short. Не ведись на поводу дебилов.

Итого: у тебя стуктура неэффективно хранит данные, ты используешь enum, зато пытаешь сделать сжатие указателей.

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

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

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

Капсом нормальные люди пишут только макросы.

С какого перепоя то? Енумы тоже.

Это что за хрень? В Си эти типы всегда называются unsigned char и unsigned short. Не ведись на поводу дебилов.

Facepalm.bmp

Лор такой лор

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

ONE, TWO и THREE — это не макросы. Не нужно их писать капсом. Капсом нормальные люди пишут только макросы.

Далеко не все codestyle’ы с вами согласны

ONE, TWO и THREE НЕ будет равны 1, 2 и 3

Не надо все воспринимать буквально, в данном случае это абстракции и абсолютное значение не важно

Это что за хрень?

Бесконечно далеки вы от мира embedde

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

man ISO Хрень

Иногда нужно число определённого размера.

char всегда 1 байт, short всегда 2.

А то где-то и char может быть 32битный

Покажи мне такой компилятор. Даже если ты такой найдешь, он ни на что не годен.

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

О. Третий. Ну давай. Такой же вопрос. Найди компилятор, где sizeof(char) != 1 или sizeof(short) != 2. Таких не существует, не существовало и никогда не будет существовать.

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

Гений, я не должен гадать, искать, показывать, шарить в исходниках компиляторов чтобы узнать сколько у меня в embedded проекте занимает переменная. Я должен написать - два байта, и чтобы было два. По стандарту. А не «найди». Никто не будет ничего искать.

Есть stdint и там типы по стандарту имеют длину, которая написана. Все, точка.

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

=)

Я остановился ещё потому что много вопросов, например ты выше сказал что динамическая память нини, значит массив структур создаётся целиком, сразу и навсегда. Значит он лишь заполняется, отправляется и чистится (чистятся значения структур)

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

Опять же, у тебя name это указатель на строку, видимо всё же аллокация есть, но в таком случае твоя идея приведения массива структур к указателю на массив char невозможна, name должен быть статическим масивом внутри стурктуры

typedef struct {
    ordet_t order;
    uint8_t name[MAXNAME];
    uint16_t lenght;
} data_t;

Но в таком случае я так понимаю lenght ранее указывавший на размер name теперь тоже не нужен так как нам известен максимальный размер name и видимо name это unsigned char текст, где не может быть например значения 0 как значимого, а значит можно просто вставить нуль терминал \0 таким образом всё превращается просто в

typedef struct {
    uint8_t name[MAXNAME];
} data_t;

или просто в

    uint8_t name[MAXNAME];

А так как нужен массив то в

   static int arrslen = 16 ;
   static int maxname = 128;
   uint8_t data[arrslen][maxname];

Отправка будет в виде просто


data[ONE] = "HELLO "
data[THREE] = "!"
data[TWO] = "WORLD"

for(int i=ONE; i < THREE && i < arrslen;i++)
{
    некий_send(data[i],strlen(data[i]+1))
}

Так как порядок важен, другое устройство уже знает в каком порядке придёт данные. И просто примет. Может придётся разве что порядок байт поменять.

А моожет всё вообще не так, и тебе реально нужны прям структуры. Тогда вариант отправить их одним блобом такой

  • 1 как сказано выше name должен быть статик массивом внутри структуры и заполнятся нужным name
  • 2 должен быть 1 статический uint8_t буфер, он будет переиспользоваться для отправки
  • 3 в структурах не должно быть дырок (ссылка про дырки выше)

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

Таким образом (не для структуры массивов, а для массива структур, для структуры массивов вообще ничего делать не надо можно её отсылать как есть, как у тебя в шапке, только данные надо изменить) можно сделать так

uint8_t senderbuffer[размер_структуры умнижить на количество структур];
uint8_t * ppp = senderbuffer; /*сохраняем указатель*/
for(int i=откуда_начинать; i < сколько_слать; i++)
{
   uint8_t * p = (uint8_t *) массив_структур[i]; /*если нет дыр то можно спокойно приводить тело структуру к массиву*/
   
   for(int j=0; j < sizeof(структура);j++)
   {
       ppp[j] = p[j];
   }
   ppp += sizeof(структура)
}

/*всё у тебя 1 строка senderbuffer заполненная телами структур*/
  некий_send_отсылающий_байты(senderbuffer, sizeof(структура) * сколько_ты_там_их_отсылаешь)

Принимающая сторона примет целиком массив байт, она знает его размер, и знает размер структур внутри, теперь она может сделать так

void writefunc(char byte)
{
   /*получаем результат и записываем кудато в input_data*/
}

uint8_t data[max]

get(writefunc) /*типа получили*/

/*теперь просто берём режем получинный массив на структуры как пирожок*/


data_t arr[лимит];
for(int i=0,j = 0; i < data_size / размер_структуры;i+=размер_структуры,j++)
{
   arr[j] = (структура*)(data+i)
}

printf("%s\n",arr[0].name);

Всё!

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

Кино не интересное? ))

зачем там order

Не стоит все воспринимать буквально ) давай назавём ее day_of_week т.е. enum нужен.

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

Да, на это обратили в самом первом сообщении

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

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

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

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

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

непуганные идеалисты :)

в некоторых архитектурах не может быть адресовано меньше 4 байт, например. и даже char там будет 4 байта, хоть тресни.

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

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

Я знаю про те задачи, которые я решаю написанием программы. И знаю, как уменьшить сношание моего мозга при чтении исходного кода. А язычки с точки зрения «расово правильно писать так» и их древние стандарты меня интересуют меньше всего на свете. Мне надо дело делать.

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

Реальность такова, что на моей платформе int16_t это два байта. И тут главное что число 16 означает тоже два байта. А short это четыре буквы и хз сколько байт (интуитивно). Остальное меня мало волнует.

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

ASN.1 – это не только телеком. Он живее всех живых и ещё нас с тобой переживёт. Тот же TLS, LDAP и много-много другого – это всё ASN.1.

Проблема с ним в другом. Из полноценных доступных реализаций компиляторов – есть только Erlang и полторы калеки на C. Всё остальное платное и закрытое. И именно в компиляторе там сила.

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

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

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

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

ну, в TLS там только ключи в этом формате. там их один раз сгенерил и юзаешь без необходимости разбирать кишочки.

и я не сомневаюсь, что такая хрень очень живуча. она эффективна. просто она не для людей сделана, а для роботов.

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

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

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

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

вот прямо в точку! Исключительный геморрой весь этот ASN.1/DER код отлаживать. А спеки ASN.1 ключей, такое ощущение, что гуманитариями написаны.

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

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

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

ASN1 - совершенно отвратительная штука. И то, что его засунули в TLS - идёт в минус этому самому TLS и его авторам - фу на них.

Единственное ему адекватное применение (и всему софту где он задействован) - хранить в музее как пример ошибочных решений на заре ИТ.

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

Нет там ничего эффективного, и ключи следовало делать в адекватном формате а не в этой пакости. И да, там не только «ключи сгенерить», тебе их ещё парсить надо, и не только из файла, а и из некоторых присланных той стороной handshake-пакетов. Речь про реализацию SSL/TLS-протокола, разумеется, а не про использование готовых. Но даже в готовых, даже в распарсенном и переведённом в человеко-читаемый вид, сертификаты выглядят совершенно непристойно.

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

покури доки/сырцы по протоколам передачи данных - по форматам хранения в базах данных (файловая система частныей случай NoSql |:) -

обычно формат размер пэйлоад

если охота самообмазываться своим домотканым форматом обмена самый дубовый вариант - 2 уровня

верхний:

-v0-размер твоего кадра - т.е буквально размер пэйлоада(с возможным плюсом вклюяая размер поля «размер твоего кадра» удобства разные)

-v1-размер в элементах твоего массива

(-v2-) тут опционально таблица смещений каждого элемента если структура не только для передачи но и непосредственного извлечения

-v3- ноль и более элементов

нижний :

(-n0-) если без таблицы смещений то длина всего элемента

-n1..nN- содержательные поля - строки для убобства \0-терминейтед

зы. бел лабс где Unix/C это связисты с докторскими дисерами в анамнезе поэтому телефония так их крута

qulinxao3 ★☆
()
Последнее исправление: qulinxao3 (всего исправлений: 3)
Ответ на: комментарий от beastie

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

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

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

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

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

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

тады lengTH поставить на первое(нулевое) место - для упрощения кода Полный размер первым необходим не просто для упрощения а для нормального фунциклирования принимающей стороны. Например хидер фиксированной длины ты можешь спокойно принять транзакцией с DMA и потом так же запустить новую транзакцию/транзакции с DMA уже зная необходимый размер пакета.

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

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

Iron_Bug ★★★★★
()