Как преобразовать структуру в массив символов в C - PullRequest
9 голосов
/ 11 января 2009

Я пытаюсь преобразовать структуру в массив символов для отправки по сети. Тем не менее, я получаю странный вывод из массива char, когда делаю.

#include <stdio.h>

struct x
{
   int x;
} __attribute__((packed));


int main()
{
   struct x a;
   a.x=127;
   char *b = (char *)&a;
   int i;
   for (i=0; i<4; i++)
      printf("%02x ", b[i]);
   printf("\n");
   for (i=0; i<4; i++)
      printf("%d ", b[i]);
   printf("\n");
   return 0;
}

Вот вывод для различных значений a.x (на X86 с использованием gcc):
127
7f 00 00 00
127 0 0 0

128
ffffff80 00 00 00
-128 0 0 0

255
ffffffff 00 00 00
-1 0 0 0

256
00 01 00 00
0 1 0 0

Я понимаю значения для 127 и 256, но почему цифры меняются при переходе на 128? Почему бы просто не быть: 80 00 00 00 128 0 0 0

Я забыл что-то сделать в процессе преобразования или я забыл что-то о целочисленном представлении?

* Примечание: это всего лишь небольшая тестовая программа. В реальной программе у меня больше структуры, лучше имена переменных, и я конвертирую в little-endian.
* Редактировать: форматирование

Ответы [ 10 ]

11 голосов
/ 12 января 2009

То, что вы видите, является сохраняющим знак преобразованием из char в int. Такое поведение объясняется тем, что в вашей системе char подписан ( Примечание: char не подписан во всех системах). Это приведет к отрицательным значениям, если битовый шаблон уступает отрицательному значению для символа. Продвижение такого символа в int сохранит знак, и int тоже будет отрицательным. Обратите внимание, что даже если вы не указали (int) явно, компилятор автоматически переведет символ в int при переходе к printf. Решение состоит в том, чтобы сначала преобразовать ваше значение в unsigned char:

for (i=0; i<4; i++)
   printf("%02x ", (unsigned char)b[i]);

В качестве альтернативы вы можете использовать unsigned char* с самого начала:

unsigned char *b = (unsigned char *)&a;

И тогда вам не понадобится какой-либо состав, когда вы печатаете его с помощью printf.

8 голосов
/ 12 января 2009

Спецификатор формата x сам по себе говорит, что аргумент является int, и поскольку число отрицательное, printf требует восемь символов, чтобы показать все четыре ненулевых байта значения int. , Модификатор 0 указывает заполнить вывод нулями, а модификатор 2 говорит, что вывод минимум должен быть длиной в два символа. Насколько я могу судить, printf не предоставляет способ указать максимальную ширину, кроме строк.

Теперь вы просто передаете char, поэтому голый x указывает функции использовать полный int, который был передан взамен - из-за продвижения аргумента по умолчанию для параметров "...". Попробуйте модификатор hh, чтобы заставить функцию обрабатывать аргумент как char:

printf("%02hhx", b[i]);
8 голосов
/ 12 января 2009

char - это подписанный тип; так с дополнением до двух, 0x80 - это -128 для 8-разрядного целого числа (т.е. байта)

5 голосов
/ 12 января 2009

Обработка вашей структуры, как если бы она была массивом символов, является неопределенным поведением. Чтобы отправить его по сети, используйте правильную сериализацию. Это боль в C ++ и тем более в C, но только так ваше приложение будет работать независимо от машин, которые читают и пишут.

http://en.wikipedia.org/wiki/Serialization#C

2 голосов
/ 12 января 2009

Преобразование вашей структуры в символы или байты так, как вы это делаете, приведет к проблемам, когда вы попытаетесь сделать ее нейтральной к сети. Почему бы не заняться этой проблемой сейчас? Существует множество различных методов, которые вы можете использовать, и все они, вероятно, будут более «переносимыми», чем те, которые вы пытаетесь сделать. Например:

  • Отправка числовых данных по сети машинно-независимым способом уже давно решается в мире POSIX / Unix с помощью функций htonl, htons, ntohl и ntohs. См., Например, страницу руководства byteorder (3) в системе FreeBSD или Linux.
  • Преобразование данных в и из полностью нейтрального представления, например JSON , также вполне приемлемо. Количество времени, которое ваши программы тратят на преобразование данных между JSON и нативными формами, может уменьшиться по сравнению с задержками при передаче по сети.
1 голос
/ 11 апреля 2010

Когда вы отправите его, просто используйте:

(символ *) & CustomPacket

конвертировать. У меня работает.

1 голос
/ 26 февраля 2010

Подпись в массиве char не является корнем проблемы! (Это проблема, но не единственная.)

Выравнивание! Это ключевое слово здесь. Вот почему вы НИКОГДА не должны пытаться обращаться со структурами как с сырой памятью. Компиляторы (и различные флаги оптимизации), операционные системы и фазы Луны делают странные и захватывающие вещи с фактическим расположением в памяти «смежных» полей в структуре. Например, если у вас есть структура с символом, за которым следует int, вся структура будет иметь в памяти ВОСЕМЬ байтов - символ, 3 пустых, бесполезных байта, а затем 4 байта для int. Машина любит делать такие вещи, чтобы структуры могли аккуратно помещаться на страницах памяти и тому подобное.

Пройдите вводный курс по машинной архитектуре в вашем местном колледже. Между тем, сериализуйте правильно. Никогда не относитесь к структурам как к массивам символов.

1 голос
/ 12 января 2009

char - это тип со знаком, так что вы видите представление с двумя комплиментами, приведение к (unsigned char *) исправит это (Роулэнд просто победил меня).

В примечании вы можете изменить

for (i=0; i<4; i++) {
//...
}

до

for (i=0; i<sizeof(x); i++) {
//...
}
0 голосов
/ 13 января 2009

Если у вас нет очень убедительных измерений, показывающих, что каждый октет драгоценен, не делайте этого . Используйте читаемый протокол ASCII, такой как SMTP , NNTP или один из многих других хороших протоколов Интернета, кодифицированных IETF.

Если вам действительно нужен двоичный формат, все равно небезопасно просто выталкивать байты в структуре, потому что порядок байтов, базовые размеры или ограничения выравнивания могут отличаться от хоста к хосту. Вы должны спроектировать свой проволочный протокол для использования четко определенных размеров и для использования четко определенного порядка байтов. Для вашей реализации, используйте макросы типа ntohl(3) или используйте смещение и маскировку, чтобы поместить байты в ваш поток. Что бы вы ни делали, убедитесь, что ваш код дает одинаковые результаты на хостах с прямым и прямым порядком байтов.

0 голосов
/ 12 января 2009

Вы можете преобразовать в массив без знака.

...