Порядок байт

/ Просмотров: 675

С проблемой пересылки чисел по сети рано или поздно сталкивается почти каждый программист. Проблема связана с тем, что по сети невозможно передать число, можно передать лишь массив байт. Проблему можно решить кривым способом: передавать число в формате ascii, символ за символом, но это ведёт к большому увеличению объёма передаваемых данных. Обычно число делят на байты, из которых оно состоит. Давайте возьмём число 287454020 и посмотрим, какие байты его составляют.

int a = 287454020;
int* b = &a;

С помощью отладчика посмотрим значение переменной b – туда должен попасть адрес в памяти переменной a. У меня оно оказалось 0x0032f8b8. Теперь вызовем окно просмотра памяти, введём наш адрес – и вот она, наша переменная.

/uploads/_pages/19/memoryorder.png

Как видим, число 287454020 состоит из байт 44 33 22 11. Но стоп, разве не наоборот должно быть? Разве 287454020 в шестнадцатеричном представлении не 11 22 33 44?

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

На самом деле эта проблема, в прошлом доставлявшая много неприятностей, сегодня успешно решена. Для передачи числа по сети (tcp/ip, UART, USB и т.д.) принят стандарт прямого порядка байт. Это означает, что наше число нужно перевернуть; а в том случае, если на нашей машине принят прямой порядок байт, ничего делать не нужно. Для этого есть специальные функции.

htonl()// – переводит 32-битное число из локального порядка байт в сетевой.
htons()// – переводит 16-битное число из локального порядка байт в сетевой.
ntohl()// – переводит 32-битное число из сетевого порядка байт в локальный.
ntohs()// – переводит 16-битное число из сетевого порядка байт в локальный. 

Все функции находятся в заголовочном файле WinSock2.h. Для того, чтобы задействовать эти функции в Visual Studio, необходимо также добавить ссылку на библиотеку Ws2_32.lib.

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

// Перекодирование word'а
#define htons(a)            ((((a)>>8)&0xff)|(((a)<<8)&0xff00))
#define ntohs(a)            htons(a)

// Перекодирование dword'а
#define htonl(a)            ( (((a)>>24)&0xff) | (((a)>>8)&0xff00) | (((a)<<8)&0xff0000) | (((a)<<24)&0xff000000) )
#define ntohl(a)            htonl(a)

Осталась самая малость. Мы научились переворачивать числа, а как разбить их на байты? Прежде всего нужно объявить массив байт, в котором будет храниться передаваемый пакет. Далее нужно действовать по одному из следующих способов.

Способ 1. Извращённо-указательный.

unsigned char buffer[4];
DWORD32 a = htonl(0x11223344);
*((DWORD32*)(&buff[0])) = a;//(1)
*((DWORD32*)buff) = a;//(2)

Выражения (1) и (2) эквивалентны, но первое более универсально: можно вписать число в любую позицию массива. Однако следует помнить, что такое решение будет работать только на той архитектуре, для которой вы проектировали свою программу. Вообще, такие преобразования не рекомендуются, и компилятор сообщит вам предупреждение Strict aliasing. Вы можете использовать их только в тех случаях, когда производительность действительно важна.

Способ 2. Красивый и правильный.

Суть в том, что используется особенность языка Си – объединения. Объединения похожи на структуры, но все элементы объединения имеют одинаковый адрес, а размер структуры равен размеру самого большого элемента. Таким образом, можно к одним и тем же данным обращаться и как к числу, и как к массиву байт. Синтаксис объединений выглядит так:

typedef union
{
    uint32 uintValue,
    float floatValue,
    char c[4]
} flchar;

Теперь можно объявить переменную типа flchar и обращаться к одному и тому же числу из-под разного типа. Например:

flchar number;
number.floatValue = 15.15;
uartSend(number.c, 4);
Оставьте комментарий!

Комментарий будет опубликован после проверки

Вы можете войти под своим логином или зарегистрироваться на сайте.

(обязательно)