应该算是理解字节序和比特序大小端模式了吧

本文中的部分结论参考自 博客字节序(byte order)和位序(bit order) ,理解之后自己重新梳理了一遍。

大端模式与小端模式不仅针对的是数据的字节序,也针对字节中的位序(比特序)。
所谓大端就是把数据的高位存放在内存的低址,这也符合人阅读的习惯,如十六进制数0x12345678,大端模式会按下图所示存放:
在这里插入图片描述

但在小端机器正好相反,即把数据的低位存放在内存的低址,如下图所示:
在这里插入图片描述

如我的电脑是intel x86架构的cpu,它就是以小端的方式在存取数据,在vs2019上调试下面代码,查看内存会得到下面的结果:
在这里插入图片描述

上面小端模式的图好像都只是把字节序颠倒了,字节内的比特序没有变化,还是和人阅读的习惯一致,导致我们会以为大小端只是针对字节序而言,比特序并没有大小端之分。但实际上并不是,例如下面的程序就说明了小端机器上的比特序也是小端的,大端机器上的比特序也是大端的。

该段程序出自 博客字节序(byte order)和位序(bit order) ,我运行的结果和小端的结果一致,因为我的电脑是小端的,我没有验证过大端的结果,因为我没有大端的运行环境,如果大家想看一下在大端上运行的结果,可以在51单片机上运行,或者在仿真的环境下运行,可以参考这篇博客 https://www.shuzhiduo.com/A/kvJ3Q9WDdg/

#include<stdio.h>
//C语言结构体中的位域:如果位域A定义在位域B之前,那么位域A总是出现在低序的比特位。
struct bit_order{
    unsigned char a: 2,
                  b: 3,
                  c: 3;
};

int main(int argc, char *argv[])
{
    unsigned char ch      = 0x79;
    struct bit_order *ptr = (struct bit_order *)&ch;

    printf("bit_order->a : %u\n", ptr->a);
    printf("bit_order->b : %u\n", ptr->b);
    printf("bit_order->c : %u\n", ptr->c);

    return 0;
}

小端机器上的结果:

bit_order->a : 1 
bit_order->b : 6 
bit_order->c : 3 

对应的比特序如下图:
在这里插入图片描述
大端机器上的结果:

bit_order->a : 1 
bit_order->b : 7 
bit_order->c : 1

对应的比特序如下图:
在这里插入图片描述

所以十六进制数0x12345678在小端机器上应该是这样的:0001 1110 0110 1010 0010 1100 0100 1000 ,在大端机器上应该是这样的:0001 0010 0011 0100 0101 0110 0111 1000
那为什么在小端机器上调试时查看内存给我们显示的是0x78563412,而不是0x1E6A2C48呢?搞得好像在谈大小端的时候只是在谈字节序,而却没有管字节内的比特序。
这是个逻辑十分诡异的问题,想回答清楚这个问题十分不好表述,我尽量把我的理解表达清楚。小端机器上的第一个字节的内容是0001 1110,如果把它显示成0x1E,这就出现了一个逻辑问题呀,因为在小端机器上,0001 1110最前面是最低位,最后面是最高位,显示成0x1E就是把前四位和后四位分别以大端的方式计算然后显示给你,这不就乱套了吗,显示出来的0x1E并不是该字节的真正的大小,只是方便你以你大端阅读的习惯将0x1E转换成二进制后,得到该字节内比特的顺序。
好吧,上述表述把我自己都搞得很晕,我们再换个角度思考。计算机是以字节为单位,也即内存是以字节进行编址,所以我们程序员在调试时使用内存查看器,面向的是字节,关心的是每个字节的表示的值的大小,而不是字节的内部的细节,所以内存查看器就把每个字节计算好了,然后以符合人阅读习惯的大端十六进制将字节的大小表示出来了,这样就屏蔽掉了比特序,也正是因为你面向的是字节,所以就不会把字节序调整成你的阅读习惯了。但如果是我们打断点查看变量的值,这个时候我们面向的就不是字节了,而是面向这个变量,所以我们关心的是这个变量的值,而不是它的细节,也就是我们不用关心这个变量是由几个字节组成,也不用关心这几个字节是大端序还是小端序,调试器直接把计算后的值显示给我们,屏蔽掉了字节序和比特序这些细节。如下图我以十六进制显示a,调试器就把a的值计算后调整成我们的阅读习惯然后再显示出来。
在这里插入图片描述

综上两个例子,如果我们的编程处于很底层,面向的是比特那么就要考虑比特序了,比如上面那个程序的结果在大小端不同的机器上运行的结果就是不一样的,这个时候你就需要解决比特序差异造成的问题。
但我们在使用c语言提供的左移和右移运算时,并不是面向比特,而是面向字节,比如我们使用左移运算符左移一位是乘以2,那是在以符合程序员阅读习惯的大端方式在思考,底层比特位可能并不是真正地向左移了一位,机器会根据相应的主机序选择是向左移还是右移,如小端机器是向右移了一位,,而大端机器则是向左移了一位。下图是在我的电脑运行下面代码的结果,说明了这个问题:

#include<stdio.h>

struct bit_order {
    unsigned char a : 1, b : 1, c : 1, d : 1, e : 1, f : 1, g : 1, h : 1;
};

int main(int argc, char* argv[])
{
    unsigned char ch = 0x1;
    struct bit_order* ptr = (struct bit_order*)&ch;
    printf("%u%u%u%u %u%u%u%u\n", ptr->a, ptr->b, ptr->c, ptr->d, ptr->e, ptr->f, ptr->g, ptr->h);
    ch = ch << 1;
    printf("%u%u%u%u %u%u%u%u\n", ptr->a, ptr->b, ptr->c, ptr->d, ptr->e, ptr->f, ptr->g, ptr->h);
    return 0;
}

在这里插入图片描述

下面讲一下网络传输是如何解决主机序差异问题,实现透明传输的。
根据 博客字节序(byte order)和位序(bit order) 所得到的结论是,上层程序只将字节序调整成大端序,而不调整比特序,然后网卡会以小端序发送每个字节的内容。总结为一句话,上层只调整字节序,网卡只调整每个字节的比特序。切记大小端是针对一个数而言,不同数之间没有大小端的概念,大端字节序是将一个数的最高位字节放在最前面,比特小端序是将最低位比特放在最前面,位的高低指的是权值的高低,而不是地址的高低,地址始终是由低到高。
下面举两个数据发送与接收的例子。

①大端机器向小端机器发送由两个字节组成的一个数:0x5678 的过程:
大端机器存储:0101 0110 0111 1000
htonl转换成大端字节序:0101 0110 0111 1000
网卡把每个字节转换成小端序然后发送:0110 1010 0001 1110
网卡把接收到的每个小端序字节都转换成自己的主机序:0110 1010 0001 1110
ntohl将为大端序字节转换成自己的主机序:0001 1110 0110 1010
小端机器存储:0001 1110 0110 1010

此过程的关键就在于上层应用程序会把字节序转换成大端序,这是互联网规定的标准序,然后以太网卡会把分别把每个字节内的比特序转换成小端序,这是统一的,接收端知道这个标准,然后根据自己的主机序做相应的转换。

②大端机器向大端机器发送由两个字节组成的一个数:0x5678 的过程
大端机器存储:0101 0110 0111 1000
htonl转换成大端字节序:0101 0110 0111 1000
网卡把每个字节转换成小端序然后发送:0110 1010 0001 1110
网卡把接收到的每个小端序字节都转换成自己的主机序:0101 0110 0111 1000
ntohl将为大端序字节转换成自己的主机序:0101 0110 0111 1000
大端机器存储:0101 0110 0111 1000

最后,看来许多关于大小端模式的博客,其中有博客指出有些计算机可能字节之间的字节序与字节内的比特序不一致,但不管一不一致,只要计算机知道什么时候该用什么序列计算就没得问题了,而我们debug的时候最多也就只会考虑字节序,很少有会出现因比特序差异而导致的问题。

以上是个人的理解,如有错误,欢迎给我指出,感激不尽🤝。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值