什么是字节序(端序、低端字节序、高端字节序、网络字节序)

前言

一个内容为12(字符串)的文本文件,它的第一个字节是什么(小端序)?如果你的回答是0x32,那你真的应该好好理解下字节序了。如下图所示,我这里的正确答案是0x31。当然如果你的回答是不一定,这似乎也是对的。背后的原因比较复杂。
在这里插入图片描述

什么是字节序

首先,字节序又称端序,顾名思义“字节的顺序”。你可能会奇怪:字节还有顺序?让我们从生活中常见概念入手吧。

生活中的数

123456,如果让你把这串数读出来的话你肯定会说:“十二万三千四百五十六。”你瞧,从左往右,这就是我们读数字时的顺序。
那有没有从右往左看数字的时候呢?当然有!如果我让你计算123456+134659你肯定会先从个位算起(除非你想给自己找麻烦)。

把数存入内存

计算机不是神仙,它和你没有心灵感应。因此为了使CPU可以读取数据,我们要先把数据存入内存再让CPU读取。内存是按地址进行访问的存储器件。在内存中,每个字节都有它对应的地址。
那么,你要如何把数0x12345678存入内存呢?
在这里插入图片描述
一种存储方式如下所示。
在这里插入图片描述
当然你也可以选择另外一种存储方式。
在这里插入图片描述
以上两种方式没有对错之分,只要在存储和读取时保持一致即可。Intel公司的CPU普遍采用第二种方式。

字节序的小端序与大端序

  • 像Intel这样把数的低字节存在内存低地址处的方案,我们称作小端序(Little-Endian),全称字节序的小端序,又称低(小)端字节序等。
  • 反之,像第一种把数的高字节存在内存低地址处的方案,我们称作大端序(Big-Endian),全称字节序的大端序,又称高(大)端字节序等。

端序

你肯定要迫不及待地问,前文一直在说的究竟是什么呢?顾名思义,端(end)就是一边、一头、一端的意思:

  • 内存中,我们以低地址为端
  • 网络传输中,我们以0时刻发送的数据为

因此字节序的大端序更精确的定义为:位置更高(重要,significant)的字节在端;相反,字节的小端序更精确的定义为位置更低(不重要)的字节在端
大端序和小端序是两种不同的端序。现在Intel公司的CPU普遍采用字节小端序(字节序的小端序)。
因此下图以字节小端序读取的结果是0x78563412,以字节大端序读取的结果是0x12345678.
在这里插入图片描述

注意: 当数据按上图在内存中存储时,不存在(至少据我所知,不存在) 0x87654321或者0x21345678这样的读取顺序.这是因为内存是以字节为最小单位进行存取的,所以字节内比特(位)的顺序总是固定不变的。当数据在网络中传输时就会涉及到字节内位的顺序

数据在网络中传输

网络字节序

根据RFC791,网络传输时(精确地说是使用IP协议进行网络传输时)以太网采用大端字节序,即先发送位置更高(重要,significant)的字节。对于0x78563412来说0x78位置更高,因此应先发送0x78。
但是由此引发了一个问题,不管CPU是大端还是小端,根据RFC791 Figure 10,只要低地址0x34存着数据0x12,就应当先发送数据0x12(实际上,网卡发送数据时也都是从低字节处开始发送,不论端序如何)。而当在小端系统中存储0x78563412时,其在内存中的表示如下图所示。按照RFC791,将发送0x12,但是按照大端序,却应对先发送0x78. 因此C语言提供了htonl和ntohl这两个函数来帮我们解决主机字节序与网络字节序之间的转换问题。在发送数据前和接收数据后先通过这两个函数对字节序进行转换。
在这里插入图片描述

网络比特序

以太网是串行传输数据的,即一次只能发送一个比特位。这样就引出了一个问题:我们知道,要想传输0x1234,需要先发送0x12这个字节,可是对于0x12我们应该先发送哪一位呢?
0x12的二进制写法是00010010B,因此有两种发送顺序。即,按照时间顺序依次发送0-0-0-1-0-0-1-0,或者反过来,发送0-1-0-0-1-0-0-0. 网络以0时刻为端,在发送0-0-0-1-0-0-1-0时比特的高位(重要的那一位)在端,我们称之为大端比特序;反之发送0-1-0-0-1-0-0-0时比特的低位(不那么重要的位)在端,我们称之为小端比特序。那么网络比特序是大端序还是小端序呢?

至于以太网在硬件层面是否真的是严格串行传输的,我并不清楚。因为ISO/IEC/IEEE 8802-3:2021居然有5千多页,鄙人才疏学浅,找不出来。

在这里插入图片描述
在这里插入图片描述
在RFC791和Linux内核中对于IP头部的定义如上图所示。可以发现,Linux为大端序和小端序提供的不同的定义。对于C的位域来说,先定义的成员位于低比特为,即0bit。对于小端系统,bit0至bit3为ihl,bit4至bit7为version。对于大端系统则反过来。根据RFC791要求,应当先发送version字段。对于网卡来说,先发送比特的高位(重要的那一位),再发送比特的低位(不重要的那一位);网卡在接收bit数据的时候,同样认为先收到MSB(Most Significant Bit),最后收到的位LSB(Least Significant Bit). 在小端系统中,MSB为bit7, LSB为bit0;在大端系统中,MSB为bit0, LSB为bit7.
综上所述,网卡会自动帮我们处理网络传输中的比特序问题。数据存取的最小单位通常是字节,而非比特,因此在一般的编程中,我们不必纠结于比特序的问题。

参考 https://blog.csdn.net/shaohui973/article/details/115766497

考考你

为了确保你真的懂了,现在考虑一个问题。假设有多字节数0x55AA需要发送,那么应该先发送哪一位或者哪一比特呢?

从字节序的角度

以太网是字节序的大端序,因此对于0x55AA,先发送MSB,即0x55. 在小端系统中,0x55位于高字节,需经过htonl转换后再发送。

从比特序的角度

首先明确肯定会先发送0x55这个字节(高端字节序)。0x55=01010101B. MSB为0,因此发送顺序为0-1-0-1-0-1-0-1. 在大端系统中,0位于bit0,1位于bit7;在小端系统中,1位于bit0,0位于bit7. 无论大端小端,网卡都会先发送MSB。

开头的问题

在这里插入图片描述
严格地说,文件中没有“大小端序”这个概念,不然,文件在不同端序CPU的系统上将无法通用,这肯定是我们不希望看到的。
在C#中,文件是按字节写入,因此不存在端序的问题——你想写什么就写什么。
在这里插入图片描述
在test1文件中,由于使用的是小端序CPU,变量的第一个字节自然是0x78,第二个字节是0x56……C#会直接把这个数组原封不动地写入文件。至于test2,字符1的utf-8编码是0x31,2是0x32……C#也会将他们原样写入文件。很多高级语言都是这样。
在这里插入图片描述
需要特别注意:在计算机中,字节序的概念被限制在CPU中,对于字节序的讨论应该和CPU息息相关。例如,汇编语言、操作系统等。 同理,关于比特序的讨论应该被限制在需要它的地方——网络的物理层/数据链路层的底部。像内存、硬盘这些设备只要符合规范,就可以在任何端序的CPU中使用。换句话说,它们本身都没有字节序的概念。
所以如果你拿着wireshark的截图问我:为啥在小端的CPU中出现了大端序,这个……我只能说它编程时就是这么设计的。毕竟这样你用起来更舒服。
在这里插入图片描述

后记

CPU是个傻瓜,它并不清楚你的意图,只是按照你的指示做事。你要做什么,你自己是最清楚的。
好比我问你10100001B到底是有符号数还是无符号数,答案是:它既是无符号数161也是有符号数-95. 至于怎么解释完全取决于你自己。
书是从左往右读的,但古代的书都是从右往左读。这并不会影响书籍的内容,只要写和读的时候顺序一致即可。

  • 15
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值