Sword 字节序详解


概述
字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。

常见序
LE little-endian
地址低位存储值的低位,地址高位存储值的高位
计算机电路先处理低位字节,效率比较高,因为计算都是从低位开始的。所以,计算机的内部处理都是小端字节序。

BE big-endian
地址低位存储值的高位,地址高位存储值的低位
大端字节序对计算机并不友好

网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采用big endian排序方式。

字节序与位移操作

字节顺序是将值存储在内存中的方式,当加载到处理器中时无论字节序如何,移位指令都将对处理器寄存器中的值进行操作。
因此从内存到处理器的加载等同于转换为大字节序,接着进行移位操作,然后将新值存储回内存中,这是小字节序字节顺序再次生效的地方。

我的理解: 整型和其他类型是不一样的,我认为计算机将整型数据从内存加载到处理器的时候会自动进行字节序转换,在处理器中中就是大端字节序。

那么啥时候需要字节序转换呢?就是我们以非整型方式获取一块内存强转为整型时,我们才需要做字节序转换。

位移操作对字节序的影响:位移操作的就是处理器中的数据,几乎全部是整型,处理器中的整型遵循大端字节序,位移操作完成之后存储到内存中,由系统自动完成字节序转换。

操作
字节序中的低位和高位只是对多字节数据有影响,对于单字节并没有字节序的概念(单个字节8bit没有高位低位的区别)
数值型多字节数据需要根据多个字节的高位低位组合得出真实的数值,高位低位的不同会导致相同序列的内存数据有不同的真实数值

说明

字符串字节序说明
字符串是一种多字节数据,但是在读取字符串的时候都是以1个字节为单位去读取的,不存在字节序的概念。

/* 本地字节序判断 */
#include <stdio.h>
#include <stdlib.h>

int main()
{
    union 
    {
        short s;
        char c[sizeof(short)];
    }un;

    un.s = 0x0102;

    if (un.c[0] == 0x01 && un.c[1] == 0x02)
    {
        printf("Big-Endian\n");
    }
    else if (un.c[0] == 0x02 && un.c[1] == 0x01)
    {
        printf("Little-Endian\n");
    }
    else
    {
        printf("Unknown\n");
    }

    getchar();
    return 0;
}

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#define STRLEN 5

//判断大小端

//核心思想:取出int的最低地址对应的一个字节,看其存放的是高位还是低位.
//如果是大端,返回1
//如果是小端,返回0;
int isBigEnd(int num) {
    int* p = &num;
    char* p2 = (char*)p;
    //如果存放的是11(高位)
    if (*p2 == 0x11) {
        return 1;
    }
    return 0;
}

void test1()
{
    unsigned char arr[2];
    arr[0] = 0x30;
    arr[1] = 0x39;

    unsigned short len1 = ((arr[0] & 0x00ff) << 8) | (arr[1] & 0x00ff);
    unsigned short len2 = *((unsigned short*)arr);
    // 打印len1 = 3039; len2 = 3930
    printf("len1 = %x; len2 = %x\n", len1, len2);

    // 打印 arr = 004FF940; plen2 = 004FF928
    printf("arr = %p; plen2 = %p\n", arr, &len2);

    /*
    设计说明:
        本地是小端字节序,为什么len1 = 3039呢?
        我的理解是计算机的寄存器中的位操作和内存存储是毫不相关的
        例如 unsigned short b = 3,那么b在寄存器中是 0x0003,但是存储在内存里是0x0300
        (arr[0] & 0x00ff) << 8)的结果是0x3000
        (arr[1] & 0x00ff的结果是0x0039
        在cpu中计算则是 0x3000 | 0x0039,结果是0x3039,将这个结果存储到内存中是0x3930

        为啥len2 = 3930?
        我的理解是*直接把内存拷贝了一份给len2指向的内存块
    */

}

void test2()
{
    unsigned char data[2] = { 0xc,0x7d };
    unsigned short s = 0;

    memcpy(&s, data, sizeof(unsigned short));

    printf("--[%x]---\n", s);    //打印 0x7d0c

    /*
    设计说明:
        memcpy属于内存操作,属于拷贝data对应的内存值到s对应的内存块上,因此s的值与本机字节序强相关。
    */

}

int main()
{
    int num = 0x11223344;
    printf("返回1表示大端\n返回0表示小端\n");
    printf("Order of bytes is %d\n", isBigEnd(num));

    test1();
    test2();
    return 0;
}

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

/* 本地字节序转换成小端字节序 */
#define gtc_bswap_constant_32(x)  \
((((x) & 0xff000000u) >> 24) | (((x) & 0x00ff0000u) >> 8) | (((x) & 0x0000ff00u) << 8) | (((x) & 0x000000ffu) << 24))

#define gtc_bswap_constant_16(x)  \
((((x) & 0xff00u) >> 8) | (((x) & 0x00ffu) << 8)

# if __BYTE_ORDER == __BIG_ENDIAN
# define htoll(x)    gtc_bswap_constant_32(x)
# define htols(x)    gtc_bswap_constant_16(x)
# else
#  if __BYTE_ORDER == __LITTLE_ENDIAN
#   define htoll(x)    (x)
#   define htols(x)    (x)
#  endif
# endif

void test()
{
    char arr[4] = { 0 };
    unsigned num1, num2;

    /*
    设计说明:
        arr是数组,arr[0]属于栈的地址低位,arr[3]属于栈的地址高位
        本机是小端字节序
        将arr内存值提取到寄存器中时,是按照小端字节序去解析的,因此解析出来的值是 0x78563412
    */
    arr[0] = 0x12;
    arr[1] = 0x34;
    arr[2] = 0x56;
    arr[3] = 0x78;

    num1 = *((unsigned int*)arr);

    num2 = htoll(num1);

    printf("num1 is %u and num2 is %u\n", num1, num2);
}

int main()
{
    test();
    return 0;
}

 

posted on 2020-03-18 20:44  寒魔影  阅读(396)  评论(0编辑  收藏  举报

导航