大小端判断

简介

《Computer_Systems_A_Programmers_Perspective(3rd)》中对大小端的描述如下:

image-20221109144610965

比如:有一个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(&reg_data_u64, reg_data, 8);
/* 小端机上得到的结果 */
memcpy得到的值: reg_data_u64 = 0x123456789abcdef
/* 大端机上得到的结果 */
memcpy得到的值: reg_data_u64 = 0xefcdab8967452301
 
original data = 0x123456789abcdef
memcpy(reg_data, &reg_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函数的时候,需要考虑

posted @ 2020-12-25 11:02  zhengcixi  阅读(204)  评论(0编辑  收藏  举报
回到顶部