Как преобразовать 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-это неопределенное поведение. Чтобы отправить его по сети, используйте правильную сериализацию. Это боль в C++ и тем более в 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. Машина любит делать такие вещи, чтобы структуры могли поместиться на страницах памяти и тому подобное.
возьмите вводный курс по архитектуре машин в вашем местном колледже. Между тем, сериализуйте должным образом. Никогда не рассматривайте структуры как массивы символов.
Если у вас очень убедительные измерения, показывающие, что каждый октет драгоценен,не делай этого. Используйте читаемый протокол ASCII, например SMTP, NNTP, или один из многих других прекрасных интернет-протоколов, кодифицированных IETF.
Если вы действительно должны иметь двоичный формат, все равно небезопасно просто выталкивать байты в структуре, потому что порядок байтов, основные размеры или ограничения выравнивания могут отличаться от хоста к хозяин. Вы должны спроектировать свой провод protcol, чтобы использовать четко определенные размеры и использовать четко определенный порядок байтов. Для вашей реализации используйте макросы типа ntohl(3)
или используйте сдвиг и маскировку, чтобы поместить байты в поток. Что бы вы ни делали, убедитесь, что ваш код дает одинаковые результаты на хостах big-endian и little-endian.