大端和小端机器通信
在通信前,通常需要约定以何种方式(大端或小端)进行通信,通常网络使用大端序进行通信,在其他系统中,由于大端机较少使用,也可约定使用小端序进行通信。本文默认使用小端序进行通信。
一、对于基本类型,一般使用 htons, htonl, htonll 分别对16位类型、32位类型、64位类型进行字节序反转,以下是 Darwin 中关于这三个函数的标准库实现。
#define htons(x) \
((__uint16_t)((((__uint16_t)(x) & 0xff00U) >> 8) | \
(((__uint16_t)(x) & 0x00ffU) << 8)))
#define htonl(x) \
((__uint32_t)((((__uint32_t)(x) & 0xff000000U) >> 24) | \
(((__uint32_t)(x) & 0x00ff0000U) >> 8) | \
(((__uint32_t)(x) & 0x0000ff00U) << 8) | \
(((__uint32_t)(x) & 0x000000ffU) << 24)))
#define htonll(x) \
((__uint64_t)((((__uint64_t)(x) & 0xff00000000000000ULL) >> 56) | \
(((__uint64_t)(x) & 0x00ff000000000000ULL) >> 40) | \
(((__uint64_t)(x) & 0x0000ff0000000000ULL) >> 24) | \
(((__uint64_t)(x) & 0x000000ff00000000ULL) >> 8) | \
(((__uint64_t)(x) & 0x00000000ff000000ULL) << 8) | \
(((__uint64_t)(x) & 0x0000000000ff0000ULL) << 24) | \
(((__uint64_t)(x) & 0x000000000000ff00ULL) << 40) | \
(((__uint64_t)(x) & 0x00000000000000ffULL) << 56)))
二、对于结构体,可以采取两种方式,假定小端机中结构体定义如下:
typedef union {
struct fields {
uint8_t a;
uint32_t b1: 8;
uint32_t b2: 12;
uint32_t b3: 12;
uint16_t c;
} fields;
char data[sizeof(struct fields)];
} basic_data;
1. 大端机中,结构体定义完全反转,在接收和发送结构体时反转整个结构体字节序。
typedef union {
struct fields {
#ifdef BIG_ENDIAN
uint16_t c;
uint32_t b3: 12;
uint32_t b2: 12;
uint32_t b1: 8;
uint8_t a;
#else
uint8_t a;
uint32_t b1: 8;
uint32_t b2: 12;
uint32_t b3: 12;
uint16_t c;
#endif
} fields;
char data[sizeof(struct fields)];
} basic_data;
使用 BIG_ENDIAN 宏表示该反转定义仅在大端机中失效。
大端机在发送和接收该结构体时,使用swap_bytes_array 反转整个结构体
void swap_bytes_array(void *ptr, size_t len) {
#ifdef BIG_ENDIAN
unsigned char *start, *end, temp;
start = (unsigned char *)ptr;
end = start + len - 1;
while (start < end) {
temp = *start;
*start = *end;
*end = temp;
start++;
end--;
}
#endif
}
void main() {
basic_data data;
data = 接收到的小端序数据;
swap_bytes_array(&data, sizeof(data));
}
反转字节序 + 反转结构体定义,便可以正常读取数据。
2. 大端机中,结构体定义中仅反转位域顺序,在接收和发送结构体时分别反转结构体中各个成员。
在这种解决方案中,只需要对位域进行反转定义,对于无位域结构体,大小端结构体定义一致,但对于较复杂结构体,这种解决方案较为繁琐。
typedef union {
struct fields {
uint16_t a;
#ifdef BIG_ENDIAN
uint32_t b3: 12;
uint32_t b2: 12;
uint32_t b1: 8;
#else
uint32_t b1: 8;
uint32_t b2: 12;
uint32_t b3: 12;
#endif
uint8_t c;
} fields;
char data[sizeof(struct fields)];
} basic_data;
void main() {
basic_data data = 接收到的数据;
// 交换 uint16_t 数据字节序
data.a = htonl(data.a);
// 由于位域不可直接寻址,故使用结构体中相邻成员找到位域起始地址
// 将位域视为字节数组进行反转
swap_bytes_array((void *)(&data.a + 1), sizeof(uint32_t));
}