大小端判断
简介
《Computer_Systems_A_Programmers_Perspective(3rd)》中对大小端的描述如下:
比如:有一个32位的数据0x01234567,在大端模式的机器上,低地址(0x100)存放的是数据的高字节(0x01);在小端模式的机器上,低地址(0x100)存放的是数据的低字节(0x67)。
测试代码
如何判断机器的大小端类型,可以通过下面的函数。
参考链接:https://www.cnblogs.com/dylancao/p/8472879.html
代码:
1 #include <stdio.h>
2
3 int checkCPU()
4 {
5 union w
6 {
7 int a;
8 char b;
9 }c;
10 c.a = 1;
11 return (c.b == 1); // 小端返回TRUE,大端返回FALSE
12 }
13
14 int main()
15 {
16 int ret = 0;
17
18 ret = checkCPU();
19 if (ret == 1)
20 printf("litttle endian\n");
21 else
22 printf("big endian\n");
23
24 return 0;
25 }
补充测试代码
#include <stdio.h>
#include <string.h>
#include <endian.h>
int get_from_data(unsigned char *src, unsigned long long *dst, unsigned short bp, unsigned short len)
{
unsigned int start_byte = 0;
unsigned int start_bit = 0;
unsigned char pos_in_byte = 0;
unsigned long long bit_val = 0;
unsigned int j = 0;
unsigned long long val = 0;
if ((src == NULL) || (dst == NULL)) {
return -1;
}
start_byte = (bp) >> 3;
start_bit = (bp) % 8;
for (j = 0; j < len; j++) {
pos_in_byte = ((start_bit + j) % 8);
bit_val = (*(src + start_byte + ((start_bit + j) >> 3)) & (((unsigned char)0x1) << pos_in_byte)) ? 1 : 0;
val |= bit_val << j;
}
*dst = val;
return 0;
}
int set_to_data(unsigned char *dst, unsigned long long src, unsigned short bp, unsigned short len)
{
unsigned int start_byte;
unsigned int start_bit;
unsigned int j = 0;
if (data == NULL) {
return -1;
}
start_byte = (bp) >> 3;
start_bit = (bp) % 8;
for (j = 0; j < len; j++) {
*(dst + start_byte + ((start_bit + j) >> 3)) &= (~(((unsigned long long)0x1) << ((start_bit + j) % 8)));
*(dst + start_byte + ((start_bit + j) >> 3)) |= (src & (((unsigned long long)0x1) << j) ? (unsigned char)1 : 0) << ((start_bit + j) % 8);
}
return 0;
}
int main(void)
{
int i = 0;
unsigned long long data_u64 = 0;
unsigned long long data_u64_tmp = 0;
unsigned char data[8] = {0};
data[7] = 0x1; /* 63:63 - 8 */
data[6] = 0x23;
data[5] = 0x45;
data[4] = 0x67;
data[3] = 0x89;
data[2] = 0xab;
data[1] = 0xcd;
data[0] = 0xef;
/* 写操作:用户传入的是uint8_t*数据 ==> uint64_t数据 */
memcpy(&data_u64, data, 8);
printf("original data = %02x %02x %02x %02x %02x %02x %02x %02x\n", data[7], data[6], data[5],
data[4], data[3], data[2], data[1], data[0]);
printf("memcpy data_u64 = 0x%llx\n", data_u64);
printf("htole64 data_u64 = 0x%llx\n", htole64(data_u64)); /* 把交换芯片当做小端 */
data_u64 = 0;
get_from_data(data, &data_u64, 0, 64);
printf("fieldop data_u64 = 0x%llx\n", data_u64);
printf("\n\n");
/* 读操作:从硬件得到的是uint64_t ==> uint8_t*数据 */
memset(data, 0x0, 8);
data_u64 = 0x123456789abcdef;
data_u64_tmp = data_u64;
printf("original data = 0x%llx\n", data_u64);
memcpy(data, &data_u64, 8);
printf("memcpy data = %02x %02x %02x %02x %02x %02x %02x %02x\n", data[7], data[6], data[5],
data[4], data[3], data[2], data[1], data[0]);
data_u64_tmp = htole64(data_u64);
memcpy(data, &data_u64_tmp, 8);
printf("htole64 data = %02x %02x %02x %02x %02x %02x %02x %02x\n", data[7], data[6], data[5],
data[4], data[3], data[2], data[1], data[0]);
shr_field64_set_to_data(data, data_u64, 0, 64); //默认按小端转换
printf("fieldop data = %02x %02x %02x %02x %02x %02x %02x %02x\n", data[7], data[6], data[5],
data[4], data[3], data[2], data[1], data[0]);
printf("\n\n");
return 0;
}
大端机上运行结果:
root@(none):/home# ./test
original data = 01 23 45 67 89 ab cd ef
memcpy reg_data_u64 = 0xefcdab8967452301
htole64 reg_data_u64 = 0x123456789abcdef
fieldop reg_data_u64 = 0x123456789abcdef
original data = 0x123456789abcdef
memcpy reg_data = ef cd ab 89 67 45 23 01
htole64 reg_data = 01 23 45 67 89 ab cd ef
fieldop reg_data = 01 23 45 67 89 ab cd ef
小端机上运行结果:
root@busybox:/home# ./test
original data = 01 23 45 67 89 ab cd ef
memcpy reg_data_u64 = 0x123456789abcdef
htole64 reg_data_u64 = 0x123456789abcdef
fieldop reg_data_u64 = 0x123456789abcdef
original data = 0x123456789abcdef
memcpy reg_data = 01 23 45 67 89 ab cd ef
htole64 reg_data = 01 23 45 67 89 ab cd ef
fieldop reg_data = 01 23 45 67 89 ab cd ef
场景
一般有以下几个地方需要进行大小端判断。
(1)按位域操作一个32bit或64bit数据,比如有一个寄存器的字段结构如下:
高位 | 低位 | 名称 |
---|---|---|
31 | 3 | high_field |
2 | 1 | middle_filed |
0 | 0 | low_field |
按位域方式访问寄存器的数据结构应该定义为如下:
typedef struct test_endian_s {
#if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
uint32_t high_field : 29;
uint32_t middle_filed : 2;
uint32_t low_field : 1;
#else
uint32_t low_field : 1;
uint32_t middle_filed : 2;
uint32_t high_field : 29;
#endif
} test_endian_t;
宏__BYTE_ORDER__ 和__ORDER_BIG_ENDIAN__是gcc或者clang支持的,可以参考:Pre-defined Compiler Macros / Wiki / Endianness (sourceforge.net)
可以使用下面的命令查看:
$ cpp -dM /dev/null
#define __DBL_MIN_EXP__ (-1021)
#define __UINT_LEAST16_MAX__ 65535
#define __ATOMIC_ACQUIRE 2
#define __FLT_MIN__ 1.17549435082228750797e-38F
#define __UINT_LEAST8_TYPE__ unsigned char
(2)位运算符如:>> 、<< 、|、;算术运算符等都没有影响
unsigned int test_data32 = 0;
unsigned char a_test_data8[4];
a_test_data8[0] = 0x12;
a_test_data8[1] = 0x34;
a_test_data8[2] = 0x56;
a_test_data8[3] = 0x78;
/* 下面的左移赋值操作在大端和小端的系统中得出的值是相同的 */
test_data32 = (unsigned int)((a_test_data8[0] << 24) + (a_test_data8[1] << 16) + (a_test_data8[2] << 8) + (a_test_data8[3]));
unsigned int test_data32 = 0x12345678;
unsigned char a_test_data8[4];
a_test_data8[0] = (test_data32 >> 24);
a_test_data8[1] = (test_data32 >> 16);
a_test_data8[2] = (test_data32 >> 8);
a_test_data8[3] = (test_data32);
/*
上述操作完成后,a_test_data8[0]中为高字节=0x12,a_test_data8[3]=0x78 低字节;大端和小端的系统中是相同的。
*/
.可以参考:大端小端问题总结 - 简书 (jianshu.io)
(3)如果使用memcpy从uint32_t类型的数据拷贝字节流拷贝到uint8_t,或者相反,需要考虑字节序。
/* 原始数据 */
unsigned char reg_data[8] = {0};
reg_data[7] = 0x1;
reg_data[6] = 0x23;
reg_data[5] = 0x45;
reg_data[4] = 0x67;
reg_data[3] = 0x89;
reg_data[2] = 0xab;
reg_data[1] = 0xcd;
reg_data[0] = 0xef;
unsigned long long reg_data_u64 = 0;
original data = 01 23 45 67 89 ab cd ef
memcpy(®_data_u64, reg_data, 8);
/* 小端机上得到的结果 */
memcpy得到的值: reg_data_u64 = 0x123456789abcdef
/* 大端机上得到的结果 */
memcpy得到的值: reg_data_u64 = 0xefcdab8967452301
original data = 0x123456789abcdef
memcpy(reg_data, ®_data_u64, 8);
/* 小端机上得到的结果 */
memcpy得到的值: reg_data = 01 23 45 67 89 ab cd ef
/* 大端机上得到的结果 */
memcpy得到的值: reg_data = ef cd ab 89 67 45 23 01
字节序转换方法
通常使用网络字节序和主机字节序的转换函数来进行字节序转换。
网络字节顺序采用大端模式进行编址。
主机字节顺序根据处理器的不同而不同。
#include <arpa/inet.h>
/*32位数据类型网络字节顺序到主机字节顺序的转换*/
uint32_t ntohl(uint32_t netlong);
/*32位数据类型主机字节顺序到网络字节顺序的转换*/
uint32_t htonl(uint32_t hostlong);
/*16位数据类型网络字节顺序到主机字节顺序的转换*/
uint16_t ntohs(uint16_t netshort);
/*16位数据类型主机字节顺序到网络字节顺序的转换*/
uint16_t htons(uint16_t hostshort);
测试:使用小端字节序主机进行测试
#include <stdio.h>
#include <arpa/inet.h>
int main(void)
{
unsigned int netlong = 0x12345678;
unsigned int hostlong = 0x12345678;
unsigned short netshort = 0x6789;
unsigned short hostshort = 0x6789;
/*32位数据类型网络字节顺序到主机字节顺序的转换*/
printf("ntohl(0x%x) = 0x%x\n", netlong, ntohl(netlong));
/*32位数据类型主机字节顺序到网络字节顺序的转换*/
printf("htonl(0x%x) = 0x%x\n", hostlong, htonl(hostlong));
/*16位数据类型网络字节顺序到主机字节顺序的转换*/
printf("ntohs(0x%x) = 0x%x\n", netshort, ntohs(netshort));
/*16位数据类型主机字节顺序到网络字节顺序的转换*/
printf("htons(0x%x) = 0x%x\n", hostshort, htons(hostshort));
return 0;
}
执行结果:
$ ./a.out
ntohl(0x12345678) = 0x78563412
htonl(0x12345678) = 0x78563412
ntohs(0x6789) = 0x8967
htons(0x6789) = 0x8967
总结
有使用位域定义的数据结构,必须区分大小端进行定义;
需要进行网络字节序传递的整型、长整型数据必须进行网络字节序转换。
使用memcpy函数的时候,需要考虑