Записывать значения разных типов данных в памяти в последовательности или массив с несколькими типами данных? - PullRequest
0 голосов
/ 25 августа 2011

Я относительно новичок в написании на C. Я сам научился использовать те ресурсы, которые я нашел в Интернете и в печати.Это мой первый настоящий проект в программировании на Си.Должен любить обучение на рабочем месте.

Я пишу код на C, который используется на процессоре цифровых сигналов Texas Instruments C6701.В частности, я пишу набор коммуникационных функций для взаимодействия через последовательный порт.

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

Мой вопрос касается того, как лучше отформатироватьданные для передачи, данные, которые я должен отправить, состоят из нескольких различных типов данных (unsigned char, unsigned int, float и т.д ...).Я не могу расширить все до плавающего (или int), потому что у меня ограниченная пропускная способность связи и мне нужно, чтобы пакеты были как можно меньше.

Я изначально хотел использовать массивы для форматирования данных,

unsigned char* dataTx[10];
dataTx[0]=char1;
dataTx[1]=char2;
etc...

Это сработало бы, за исключением того, что не все мои данные - char, некоторые - беззнаковые int или unsigned short.

Для обработки short и int я использовал битовое смещение (позволяет игнорировать little-endian против big-endianна данный момент).

unsigned char* dataTx[10];
dataTx[0]=short1>>8;
dataTx[1]=short1;
dataTx[2]=int1>>24;
dataTx[3]=int1>>16;
etc...

Однако я считаю, что другой (и лучший?) способ сделать это - использовать указатели и арифметику указателей.

unsigned char* dataTx[10]
*(dataTx+0) = int1;
*(dataTx+4) = short1;
*(dataTx+6) = char1;
etc...

Мой вопрос ( наконец ): какой метод (сдвиг битов или арифметика указателей) является более приемлемым?Кроме того, один быстрее работать?(У меня также есть ограничения во время выполнения).

Мое требование: данные должны располагаться в памяти последовательно, без пробелов, разрывов или заполнения.

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

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

Ответы [ 4 ]

0 голосов
/ 25 августа 2011

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

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

unsigned char dataTx[1024];
unsigned char *dst = dataTx;

dst += st_int2(short1, dst);
dst += st_int4(int1, dst);
dst += st_char(str, len, dst);
...

В функциональной форме эти функции могут быть:

size_t st_int2(uint16_t value, unsigned char *dst)
{
    *dst++ = (value >> 8) & 0xFF;
    *dst   = value & 0xFF;
    return 2;
}

size_t st_int4(uint32_t value, unsigned char *dst)
{
    *dst++ = (value >> 24) & 0xFF;
    *dst++ = (value >> 16) & 0xFF;
    *dst++ = (value >>  8) & 0xFF;
    *dst   = value & 0xFF;
    return 4;
}

size_t st_char(unsigned char *str, size_t len, unsigned char *dst)
{
    memmove(dst, str, len);
    return len;
}

Конечно, такие функции делают код скучным;с другой стороны, они также снижают вероятность ошибок.Вы можете решить, должны ли имена быть st_uint2() вместо st_int2() - и, действительно, вы можете решить, должны ли длины быть в байтах (как здесь) или в битах (как в типах параметров).Пока вы последовательны и скучны, вы можете делать как хотите.Вы также можете объединить эти функции в более крупные, которые упаковывают целые структуры данных.

Операции маскирования (& 0xFF) могут не потребоваться в современных компиляторах.Когда-то давным-давно я, кажется, помнил, что они были необходимы, чтобы избежать случайных проблем с некоторыми компиляторами на некоторых платформах (поэтому у меня есть код 1980-х годов, который включает такие операции маскирования).Упомянутые платформы, вероятно, успокоились, поэтому, с моей стороны, это может быть чистой паранойей, что они (все еще) там.

Обратите внимание, что эти функции передают данные в порядке с прямым порядком байтов.Функции можно использовать «как есть» как на машинах с прямым порядком байтов, так и на машинах с прямым порядком байтов, и данные будут правильно интерпретироваться для обоих типов, поэтому вы можете использовать различные аппаратные средства для передачи данных по проводам, используя этот код, и будетнет недопонимания.Если у вас есть значения с плавающей точкой для передачи, вам нужно немного больше беспокоиться о представлениях по проводам.Тем не менее, вам, вероятно, следует стремиться к тому, чтобы данные передавались в независимом от платформы формате, чтобы взаимодействие между типами микросхем было как можно более простым.(Именно поэтому я использовал размеры типов с числами в них; в частности, 'int' и 'long' могут означать разные вещи на разных платформах, но 4-байтовое целое число со знаком остается 4-байтовым целым числом со знаком, даже если выне повезло - или повезло - достаточно иметь машину с 8-байтовыми целыми числами.)

0 голосов
/ 25 августа 2011

Самый простой и традиционный способ решения вашей проблемы - настроить данные, которые вы хотите отправить, и затем передать указатель на ваши данные в процедуру передачи. Наиболее распространенным примером будет подпрограмма POSIX send():

ssize_t send(int socket, const void *buffer, size_t length, int flags);

Что для вашего случая вы можете упростить до:

ssize_t send(const void *buffer, size_t length);

А затем используйте что-то вроде:

send(&int1, sizeof int1);
send(&short1, sizeof short1);

Чтобы отправить это. Пример (но довольно наивный) реализации для вашей ситуации может быть:

ssize_t send(const void *buffer, size_t length)
{
  size_t i;
  unsigned char *data = buffer;

  for (i = 0; i < length; i++)
  {
     dataTx[i] = data[i];
  }
}

Другими словами, используйте автоматическое преобразование в void *, а затем обратно в char *, чтобы получить побайтный доступ к вашим данным, а затем отправьте их соответствующим образом.

0 голосов
/ 25 августа 2011

Длинный вопрос, попробую более короткий ответ.

Не продолжай * (dataTx + 4) = short1; и т.д., потому что этот метод может не сработать, потому что большинство микросхем может выполнять чтение / запись только в некоторых выровненных позициях. Вы можете получить доступ к 16-битным позициям, выровненным по 2, и 32-битным по позициям, выровненным по 4, но возьмите пример: «int32 char8 int32» - второй int32 имеет позицию (dataTx + 5) - что не является 4-байтовым выровнен, и вы, вероятно, получите «ошибку шины» или что-то в этом роде (в зависимости от используемого процессора). Надеюсь, вы понимаете эту проблему.

1-й способ - вы можете попробовать struct, если объявите:

struct
{
    char a;
    int b;
    char c;
    short d;
};

Теперь у вас нет проблем, так как сам компилятор позаботится о выравнивании структуры. Конечно, читайте о параметрах, связанных с выравниванием, в вашем компиляторе (если это gcc, то это просто называется выравниванием), потому что, вероятно, есть настройка, которая вызывает некоторое выравнивание структурных полей или упаковку структурных полей. GCC может даже определить выравнивание для структуры (больше здесь ).

Другой способ - использовать некоторый «буферный подход» - что-то наподобие ответной статьи Карла Норума (я не буду дублировать этот ответ), но также рассмотреть возможность использования вызовов memcpy (), когда больше данных копируется (например, long long или string), так как это может быть быстрее, чем копирование побайтно.

0 голосов
/ 25 августа 2011

Возможно, вы захотите использовать массив союзов.

...