1. 程式人生 > 其它 >位元組序(大小端)

位元組序(大小端)

技術標籤:網路c語言

文章目錄

什麼是位元組序

位元組序,簡單來說,就是位元組的順序,指的超過一個位元組的資料型別在記憶體中儲存的順序(一個位元組顯然不需要順序)。
位元組序在不同的主機平臺上分為大端位元組序、小端位元組序。

小端位元組序

高位位元組資料存放在高地址處,低位資料存放在低地址處。linux和windows平臺一般都採用小端位元組序。
採用union可以打印出記憶體中的位元組順序:

void show_little_endian
() { union{ char byte[4]; int num; }test_num; test_num.num = 0x12345678; printf("[0]:%p:0x%X\n" "[1]:%p:0x%X\n" "[2]:%p:0x%X\n" "[3]:%p:0x%X\n", &test_num.byte[0], test_num.byte[0], &
test_num.byte[1], test_num.byte[1], &test_num.byte[2], test_num.byte[2], &test_num.byte[3], test_num.byte[3]); /* [0]:0x7ffdeb35bb94:0x78 [1]:0x7ffdeb35bb95:0x56 [2]:0x7ffdeb35bb96:0x34 [3]:0x7ffdeb35bb97:0x12*/ }

小端位元組序

如上所示,低位元組0x7ffdeb35bb94存放的是地位0x78,高位元組0x7ffdeb35bb97存放的是高位0x12。

大端位元組序

大端就是將高位位元組放到記憶體的低地址端,低位位元組放到高地址端。一般網路上都使用大端位元組序進行二進位制資料的傳輸。

void show_big_endian()
{
    union{
        char byte[4];
        int num;
    }test_num;

    test_num.num = htonl(0x12345678);

    printf("[0]:%p:0x%X\n"
            "[1]:%p:0x%X\n"
            "[2]:%p:0x%X\n"
            "[3]:%p:0x%X\n",
            &test_num.byte[0], test_num.byte[0],
            &test_num.byte[1], test_num.byte[1],
            &test_num.byte[2], test_num.byte[2],
            &test_num.byte[3], test_num.byte[3]);
}
    /*
    [0]:0x7ffd7f6abbd4:0x12
    [1]:0x7ffd7f6abbd5:0x34
    [2]:0x7ffd7f6abbd6:0x56
    [3]:0x7ffd7f6abbd7:0x78
    */

大端位元組序

如上所示,低位元組0x7ffd7f6abbd4存放的是低位0x12,高位元組0x7ffd7f6abbd7存放的是高位0x78。

主機位元組序

指執行程式的主機的位元組序,可以採用下面方式獲取主機的位元組序:

bool isLittlEndian()
{
    uint32_t n1 = 1;
    uint32_t n2 = htonl(n1);
    return n1 != n2; // 如果htonl生效(n2!=n1),則說明主機是小端,n2則是按大端位元組序排序的
}

bool isLittlEndian2()
{
    union{
        char byte[4];
        int num;
    }test_num;
    test_num.num = 0x01u;
    return test_num.byte[0] == 0x01u; // 通過低位元組是地位來判斷
}

為什麼要有大小端

小端位元組序的優勢:計算機電路先處理低位位元組,採用小端位元組序效率比較高。
大端位元組序的優勢:資料的儲存方式和人的閱讀方式一致,更利於直接閱讀。

大小端的轉換

將對應的位元組順序顛倒即可
大小端的轉換

#define BigLittleSwap16(A)        ((((uint16_t)(A) & 0xff00) >> 8) | \
                                                       (((uint16_t)(A) & 0x00ff) << 8))


#define BigLittleSwap32(A)        ((((uint32_t)(A) & 0xff000000) >> 24) | \
                                                       (((uint32_t)(A) & 0x00ff0000) >> 8) | \
                                                       (((uint32_t)(A) & 0x0000ff00) << 8) | \
                                                       (((uint32_t)(A) & 0x000000ff) << 24))

#define BigLittleSwap64(A)        ((uint64_t)BigLittleSwap32((A & 0xFFFFFFFF)) << 32 |  \
                                                       (uint64_t)BigLittleSwap32((A >> 32)))

double、float這類浮點數也需要進行位元組序轉換,轉換方法參考同大小的int

常見型別的主機位元組序和網路位元組序轉換

Linux 和 windows 都提供了介面對資料進行轉換

uint32_t htonl(uint32_t hostlong);    // 32位資料(int,int32_t,unsigned int,uint32_t,float)主機序轉網路位元組序
uint16_t htons(uint16_t hostshort);    // 16位資料(short,int16_t,unsigned short,uint32_t,float)主機序轉網路位元組序
uint32_t ntohl(uint32_t netlong);    // 32位資料(int,int32_t,unsigned int,uint32_t,float)網路位元組序轉主機序
uint16_t ntohs(uint16_t netshort);    // 16位資料(int,int32_t,unsigned int,uint32_t,float)網路位元組序轉主機序    

int64_t,uint64_t,double可以參考BigLittleSwap64
需要注意的是,windows和linux的long型別定義不同,windows定義的是32位,而linux定義的是64位,網路傳輸時會有歧義,應該避免使用

結構體的大小端轉換

結構體內的每個元素都是獨立的,需要對每個元素進行大小端轉換。