大小端存储的疑问

2021-10-20 15:00:55 +08:00
 x97bgt

基本理解是,字节(十六进制 xx )是最小的处理单位。

如果一个数字是 4 个字节,假设是0x12345678,假设通过网络传输过来后,计算机

计算机是从低电路读取数据的。上面的78 | 56 | 34 | 1278 | 56 | 34 | 12,从左往右,电位是逐渐递增的,也就是读的顺序是从左读到右。所以大端下,计算机先读数字的高位,小端下先读数字的低位。

存储数字是这样的。但有疑问:

3197 次点击
所在节点    程序员
26 条回复
Mitt
2021-10-20 15:06:31 +08:00
按我的理解大小端也是个约定,跟字节流无关,你写和读的时候才需要区分,存储不需要

两个数字的问题你的场景其实是连续内存,那么两个数字可以按每个数字去看就是 34 | 12 | 78 | 56,当然前提是 int16,如果是 int32 就应该是 34 | 12 | 00 | 00 和 00 | 00 | 78 | 56
imes
2021-10-20 15:11:44 +08:00
让我想起来我之前被 Endianness 坑过,摄像头是 Big-End,主控是 Little-End
x97bgt
2021-10-20 15:12:18 +08:00
@Mitt 但你写”进“文件里,大小端的不同,写进的顺序是不是也不一样?

另外你说的第二点,也就是,读写前,数字的二进制长度都是已经先确定的了。靠这个保证来区分数字间的间隔?
Mitt
2021-10-20 15:17:54 +08:00
@x97bgt #3 这是内存布局,没有大小端区别,你从 buffer 读始终是从左到右一位一位读的,另外数字是的,每个数字也就是二进制长度都是固定的,如果不固定常见的作法是前面加一个标识接下来要读多少个字节,而数字对应的编程是 int 有 int32 64 8 等等,int8 就是 byte 也就 8 个 bit,存储的时候没有什么整数浮点区分,都是 bytes
leoleoasd
2021-10-20 15:18:38 +08:00
数据就是 bit stream,没有类型,可以按照任意类型解读。
如果对于以下数据(从左到右地址从低到高,最左边是 0 ):
0x12 0x34 0x56 0x78
那么从地址 0 读 int ( 4 字节),大端序机器都出来就是 12345678,小端序机器都出来就是 78563412 。
从地址 0 读 short ( 2 字节),分别就是 1234 和 3412.
从地址 1 读 short ( 2 字节),分别就是 3456 和 5634.

你的问题:如何区分两个数字(怎么知道是两个,不是一个):没法区分。所以要约定好是几个。只要数据发送方和接受方约定好是两个,按照两个存,按照两个取,就不会有问题。

储存规则(包括硬盘、内存、网络)和上层逻辑无关,只要存和取用的统一套规则就不会出问题。
chanchancl
2021-10-20 15:19:29 +08:00
大小端只是你已知数据,该如何进行存储和传送的问题(比如内存、硬盘、网络)
如何区分数字?存储系统本质上存的都是 01 序列,是不管区分的,区分靠的是你上层的 protocol
对于字节流,没必要区分,因为计算机存储的最小寻址单位就是字节了
jorneyr
2021-10-20 15:40:41 +08:00
怎么知道它是两个,而不是一个数字?
这个属于解析了,只有在写程序的时候才知道,也就是写程序的时候你很明确的说读取一个 32 位 int 还是读取一个 64 位 int,存储是没有逻辑的,使用才有逻辑,例如 Java 里的 DataInputStream.readInt() 和 DataInputStream.readLong()。
x97bgt
2021-10-20 16:01:55 +08:00
@Mitt @leoleoasd @chanchancl
「机器读出来」,这句话具体表示什么? 是机器把数据把数据从磁盘里读到内存 /寄存器?

大小端是表示内存的字节顺序有大小端之分吗?磁盘里存储的东西都一样。但读出来后,内存里存放的字节顺序,取决于大小端的不同。这样理解对么?
leoleoasd
2021-10-20 16:06:44 +08:00
@x97bgt #8 磁盘里和内存里储存的是字节序列,没有类型,没有端序。

端序体现在『如何把 0x12345678 这个 int 类型数据转换为字节序列』。大端序和小端序转换结果不同。
x97bgt
2021-10-20 16:25:33 +08:00
@leoleoasd
但我把 int 变成了 byte,然后存到内存 /磁盘里,顺序也会受大小端影响吧?
dong568789
2021-10-20 17:28:22 +08:00
你理解的没错,int 类型的大端和小端在磁盘上的顺序是不一致的,所以才需要双方协商好编 /解码的 protocol
araaaa
2021-10-20 17:34:39 +08:00
只有整型才有大小端
index90
2021-10-20 17:40:04 +08:00
1. 小端是 34 | 12 | 78 | 56
index90
2021-10-20 17:47:06 +08:00
1. 小端是 34 | 12 | 78 | 56 ;如何区分两个数字与大小端无关,与你用的类型有关。如果你从地址 0 读取两个 int16,那么就是两个数字,如果你从地址 0 读取一个 int32,那么就是一个数字
2. 有,记得数据发送时从高地址位先行,接收端也是从高地址位开始接收。
leoleoasd
2021-10-20 18:17:27 +08:00
@x97bgt #10 对
littlefishcc
2021-10-20 18:38:16 +08:00
大小端是电脑对 short 和 int 读写处理规则而已,这个跟 JSON 和 xml 解析类似的原理,只是大小端针对二进制数据处理,所以不是那么直观理解。
这种描述不是很好理解,你可以写代码测试。
C 语言
int i = 0x12345678;// 如果小端 CPU 跑的程序内存二进制是:0x78562312
int i = 0x12345678;// 如果大端 cpu 跑的程序内存二进制值是:0x12345678

int i = 0x12345678;
unsigned char* c = (unsigned char*)&(i);
for( int n = 0; n < 4; n++){
printf("%x",c[n]);
}

window pc 电脑 一般是小端:
打印结果 :78563412

我的理解:
我们写代码 int i = xxxx,但编译器根据 cpu 类型换成对应的汇编代码。32 位的程序,汇编中有 word dword 来表示 2 个字节和 4 个字节。但由于历史发展原因,汇编表示 word dword 存放顺序不统一,才产生大小端由来,cpu 厂商不一样,想法一样,这个跟当初浏览器 保准不同一类似。
总结:
大小端问题其实汇编层问题,所以我们用高级语言时候是了解不到整数在具体存放方式,所以不是那么容易理解。

我们 char 根本没有大小端问题,因为汇编层只是针对 word dowd 存放不一致。但不要理解 int i = 0x12345678;
writeFile((char*)&i); 这样子是没有解决内存的问题,而是你要把 i 分解成 char c1 = 0x12, c2 = 0x34, c3= 0x56,
c4 = x078, 然后分别写入到字节流去。

如果你传递数据是 char 或者准确说字符串是不用关心大小端问题,你看 http 传送根本不用关心大小端,只有传递二进制
数据结构

{
int id;
int len;
char [xxxx] packet;
}
类似含有 short 和 int,long 字节形识,就要考虑大小端问题。
上面是我自己对大小端理解,可能一些解释不是很正确,麻烦谅解。


场景:
大小端一般用于网络通信,因为用户电脑 CPU 可能不一样,所以通信字节必须统一(可以统一为小端也可以统一为大端,看客服端与服务器约定),现在高级语言都有 buffer,对整数或者 long 或 short 进行大小端操作方法,非常方便。
xujinkai
2021-10-20 19:21:59 +08:00
字节流没有大小端。
数字的话,在写入和解析的时候你是知道长度的。你举的例子是两个两字节的数字,所以是第一个。如果是两个四字节的数字,最后就是 0x3412000078560000 。
至于如何确定到底是两字节还是四字节,那是提前约定好的。
ecnelises
2021-10-20 19:48:59 +08:00
大小端的最小单位是字节而不是位,而且针对的是内存操作中的单个数据,比如 int 、long 、double 等。所以如果你的数据单位也只有一个字节,就不用操心字节序问题。而多个 int 、long 组成的数组,元素之间的顺序也和字节序无关。

怎么理解呢?假设你有这样一个数组,其中两个 32 位 int,值分别是 0x01234567 和 0x89ABCDEF,在小端序机器上,内存里存的字节是 0x67 0x45 0x23 0x01 0xEF 0xCD 0xAB 0x89 ;而大端序上则是 0x01 0x23 0x45 … 0xCD 0xEF

所以你可以看到,大端序更直观,而两者各有各的好处。如今小端序更流行更像是 x86 造成的习惯原因。https://stackoverflow.com/questions/5185551

你想问的是,数据真正存储到文件里用的是什么字节序。严格来说这个问题本身是不存在的,因为字节序描述的是内存中的顺序。但不妨不准确地理解为大端序,因为从硬盘读取到内存里的都是一段一段的字节流,然后你可以随意「理解」为任意类型来读。

但是我们还是没有理解,为什么会有字节序这种东西存在,如何「理解」内存难道不是程序的自由吗?为什么要机器来规定?不是的。字节本身是没有意义的。两组 4 个字节,当作 float 和当作 int 进行相加,结果的字节是完全不一样的。所以机器需要一种确定的机制来从内存读数据到寄存器,或者直接从两个内存地址读数据进行操作。所以字节序只对机器指令集能够原生支持的数据类型有意义。
realradiolover
2021-10-20 21:13:42 +08:00
首先,从网卡、内存、磁盘的角度看,传输、保存的永远都是字节流。给字节流中的字节赋予意义,例如数据类型( 32 位整型、浮点数.....)、字符串、位图数据....是应用程序的责任。和数据类型配套衍生出来的是大小端规则。CPU 处理数据前,会一次性读入 16 位或者 32 位或者 64 位数据到高速缓存,再到 ALU 等。大端机 CPU 会认为低比特位存放数值高位,小段机正相反。为了适配 CPU 这种约定,保证 CPU“看到”逻辑上正确的数值,需要事先组织这个数值在内存里的排列。这是大小端机制的来源。

大小端在 bit 层次上就存在的。假设内存地址从左到右增加,0x3 在小端机器上保存为 11000000,在大端机上保存为 00000011,在以太网等电信网络设施中、USB 串口中,按照 0->0->0->0->0->0->1->1 的顺序发送。

所有网卡的驱动,如果主机是小端,会自动转换字节内部的 bit 序(网络传输顺序为大端序);但是 Byte 间的顺序(字节序)保持不变。毕竟网卡只能看到字节流,它不知道也不需要关心这些字节代表的是字符串,还是 uint32_t 。

字节序转换,是应用层的事情(例如常见的 ntohs,ntohl 调用)。字节内部的 bit 序已经由网卡处理好。

容易混淆的是,我们在分析类似问题的时候会用到 16 进制数字,经常把数学意义上的数字,和内存表示法意义上的数字书写混为一谈。数学意义上的数字的书写,永远是左边高位右边低位。内存表示法意义上的数字,一般是左边低地址右边高地址,所以大端小端,写出来是不一样的。

另一个容易混淆的是,我们一般书写 16 进制数字的最小单位是字节,而不是比特。这就意味这它无法表征字节内部的 bit 序。数字 0x1234,在大端机器上的内存表示为 0x12 0x34,在小端机器上的内存表示为 0x34 0x12 。而 0x34 这一个字节,在大端、小端、网络中的内存表示法是一致的:0x34 。如果用二进制内存表示法,区别就很明显了。


楼主的提问是应用程序对字节流的定义,和字节流本身的大小端机制并没有关系。
x97bgt
2021-10-20 21:25:55 +08:00
@littlefishcc @ecnelises
- 读字节流的顺序都是固定的,但会按大小端的不同来“理解”。
- 写字节流,按怎么顺序写的,也是受大小端影响的。

不知道我这样理解对不对。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/809221

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX