梗概
本文介绍了字节序和比特序中的大端和小端,不同的cpu架构有不同的内存数据读写方式,但程序的数值计算发生在寄存器上,cpu通过在寄存器和内存的数据传输转换中对用户隐藏了大端小端;数据在网络中发送的时候会统一转成大端,在主机接收的时候根据主机的CPU架构自行转换,这维护了不同CPU架构下的数据通信;但仍有缺陷的是,如果在跨平台的程序中使用了位域,由于涉及到比特序块数据反转问题,需要手动处理。
字节序: 大端数和小端数
大端数(big-endian)和小端数(little-endian)是两种不同的数据存储方式,这两个中文会让人绕晕,从英文上是比较好理解的。
big-endian:大的部分(数的高位)在存储单元的尾部
little-endian:小的部分(数的低位)在存储单元的尾部
来看一张内存图, 从内存单元上来看排列顺序是这样的,也就是从下往上增长,从右往左增长,当一个指针指向一个int型(四内存单元)的变量时,指针的地址是地址最低的内存单元,CPU从内存中取数的时候是永远都是从最低位(右边)开始一个字节一个字节的取,将它放到寄存器中(寄存器永远是符合人类直觉的LE little-endian存储方式, 内存上的大小端存储经过CPU的加载到寄存器时被转换成LE little-endian, 从而隐藏了内存上的大小端),最左边是就是取一个数的尾部.
image.png
所以就很好理解了,对于一个数int a=0x01020304
内存地址 | 0x00000004 | 0x00000003 | 0x00000002 | 0x00000001 |
---|---|---|---|---|
big-endian | 04 | 03 | 02 | 01 |
little-endian | 01 | 02 | 03 | 04 |
而字节序在CPU与程序中的本质是,字节序是在数据的存储字节大于1的时候,从内存加载一个字节到寄存器上的不同加载方式, 但使用数据的时候,无论是读short ,int 还是long long最终真正使用的是寄存器上的le little endian 的值,所以大小端对人是隐藏的,但当你一个一个去看内存的值的时候,就是直接加载到最低位,这时候就不同了。
image.png
验证机器是big-endian还是little-endian
union num{
int temp;
char c[4];
};
int main(){
union num number;
number.temp = 0x01020304;
cout<<int(number.c[3])<<endl; //如果是1则是小端,4是大端
return 0;
}
网络传输中的字节序
网络中传输的字节序统一为big endian,主机上的数据在send的时候会通过hton进行转换,到接收端recv的时候ntoh恢复回来,如果hton在big endian架构的系统上是一个空宏
比特序
字节序是在数据的存储字节大于1的时候,从内存加载一个字节到寄存器上的不同加载方式。而比特序,则是加载一个8位的bit的时候到寄存器上的不同方式,一般而言比特序和主机的字节序保持一致是大端或者小端
image.png image.png
网络传输中的比特序和跨平台程序
网络传输过程中,传输协议做了hton的字节序转换,而网卡则会替我们将比特序统一转换到大端,接收端网卡接收的时候根据自己的cpu架构进行转换,对于cpu,内存而言也是不可见的;但是在使用c的位域的时候,会出现问题,对于如下的结构体,
struct A{
uint16_t first:4;
uint16_t second:4;
}A;
从小端序传到大端序的主机上,比特序发生的倒序,cpu在读取的进寄存器时候可以解决,但是 first 和 second对应数值的比特块却完全颠倒了,这就需要手动矫正:
image.png
所以在涉及到位域的跨平台设计中常会看到如下内容,如此一来,在不同平台下,就会发生反转
struct A{
#if defined(__LITTLE_ENDIAN_BITFIELD)
uint16_t first:4;
uint16_t second:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
uint16_t second:4;
uint16_t first:4;
}A;
image.png
具体参考:
https://www.linuxjournal.com/article/6788
https://blog.csdn.net/liuxingen/article/details/45420455/
网友评论