Как преобразовать struct в массив char в C

Я пытаюсь преобразовать структуру в массив символов для отправки по сети. Тем не менее, я получаю некоторые странные выходные данные из массива 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 ответов


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

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

printf("%02hhx", b[i]);

то, что вы видите, - это знак, сохраняющий преобразование из char в int. Поведение является результатом того, что в вашей системе char подписан (Примечание: 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.


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


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

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


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

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

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

на боковой ноте вы можете изменить

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

to

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

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

выравнивание! Это ключевое слово. Вот почему вы никогда не должны пытаться рассматривать структуры как необработанную память. Комплиеры (и различные флаги оптимизации), операционные системы и фазы луны делают странные и захватывающие вещи для фактического местоположения в памяти "смежных" полей в структуре. Например, если у вас есть структура с символом, за которым следует int, вся структура будет восемь байтов в памяти - char, 3 пустых, бесполезных байта, а затем 4 байта для int. Машина любит делать такие вещи, чтобы структуры могли поместиться на страницах памяти и тому подобное.

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


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

(char*)&CustomPacket

преобразовать. Работать на меня.


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


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

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