8

大小端存储的疑问

 2 years ago
source link: https://www.v2ex.com/t/809221
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

V2EX  ›  程序员

大小端存储的疑问

  x97bgt · 12 小时 56 分钟前 · 1354 次点击

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

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

  • 大端存储:按 12 | 34 | 56 | 78 的顺序存储这个数字
  • 小端存储:按 78 | 56 | 34 | 12 的顺序存储这个数字。

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

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

  • 如果连续存储两个数字,那会是什么样子的?比如0x12340x5678,最后小端的存储内容是12 | 34 | 56 | 78吗?小端是34 | 12 | 78 | 56 还是 78 | 56 | 34 | 12?大小端咋区分这两个数字(怎么知道它是两个,而不是一个数字?)
  • 如果是字节流,也有大小端存储之分吗?我的理解是,来一个字节就存一下,似乎没必要分大小端。
24 条回复    2021-10-21 03:10:06 +08:00

Mitt

Mitt   12 小时 50 分钟前

按我的理解大小端也是个约定,跟字节流无关,你写和读的时候才需要区分,存储不需要

两个数字的问题你的场景其实是连续内存,那么两个数字可以按每个数字去看就是 34 | 12 | 78 | 56,当然前提是 int16,如果是 int32 就应该是 34 | 12 | 00 | 00 和 00 | 00 | 78 | 56

imes

imes   12 小时 45 分钟前

让我想起来我之前被 Endianness 坑过,摄像头是 Big-End,主控是 Little-End

x97bgt

x97bgt   12 小时 44 分钟前

@Mitt 但你写”进“文件里,大小端的不同,写进的顺序是不是也不一样?

另外你说的第二点,也就是,读写前,数字的二进制长度都是已经先确定的了。靠这个保证来区分数字间的间隔?

Mitt

Mitt   12 小时 39 分钟前

@x97bgt #3 这是内存布局,没有大小端区别,你从 buffer 读始终是从左到右一位一位读的,另外数字是的,每个数字也就是二进制长度都是固定的,如果不固定常见的作法是前面加一个标识接下来要读多少个字节,而数字对应的编程是 int 有 int32 64 8 等等,int8 就是 byte 也就 8 个 bit,存储的时候没有什么整数浮点区分,都是 bytes

leoleoasd

leoleoasd   12 小时 38 分钟前

数据就是 bit stream,没有类型,可以按照任意类型解读。
如果对于以下数据(从左到右地址从低到高,最左边是 0 ):
0x12 0x34 0x56 0x78
那么从地址 0 读 int ( 4 字节),大端序机器都出来就是 12345678,小端序机器都出来就是 78563412 。
从地址 0 读 short ( 2 字节),分别就是 1234 和 3412.
从地址 1 读 short ( 2 字节),分别就是 3456 和 5634.

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

储存规则(包括硬盘、内存、网络)和上层逻辑无关,只要存和取用的统一套规则就不会出问题。

chanchancl

chanchancl   12 小时 37 分钟前

大小端只是你已知数据,该如何进行存储和传送的问题(比如内存、硬盘、网络)
如何区分数字?存储系统本质上存的都是 01 序列,是不管区分的,区分靠的是你上层的 protocol
对于字节流,没必要区分,因为计算机存储的最小寻址单位就是字节了

jorneyr

jorneyr   12 小时 16 分钟前

怎么知道它是两个,而不是一个数字?
这个属于解析了,只有在写程序的时候才知道,也就是写程序的时候你很明确的说读取一个 32 位 int 还是读取一个 64 位 int,存储是没有逻辑的,使用才有逻辑,例如 Java 里的 DataInputStream.readInt() 和 DataInputStream.readLong()。

x97bgt

x97bgt   11 小时 55 分钟前

@Mitt @leoleoasd @chanchancl
「机器读出来」,这句话具体表示什么? 是机器把数据把数据从磁盘里读到内存 /寄存器?

大小端是表示内存的字节顺序有大小端之分吗?磁盘里存储的东西都一样。但读出来后,内存里存放的字节顺序,取决于大小端的不同。这样理解对么?

leoleoasd

leoleoasd   11 小时 50 分钟前

@x97bgt #8 磁盘里和内存里储存的是字节序列,没有类型,没有端序。

端序体现在『如何把 0x12345678 这个 int 类型数据转换为字节序列』。大端序和小端序转换结果不同。

x97bgt

x97bgt   11 小时 31 分钟前

@leoleoasd
但我把 int 变成了 byte,然后存到内存 /磁盘里,顺序也会受大小端影响吧?

dong568789

dong568789   10 小时 28 分钟前

你理解的没错,int 类型的大端和小端在磁盘上的顺序是不一致的,所以才需要双方协商好编 /解码的 protocol

araaaa

araaaa   10 小时 22 分钟前

只有整型才有大小端

index90

index90   10 小时 16 分钟前

1. 小端是 34 | 12 | 78 | 56

index90

index90   10 小时 9 分钟前

1. 小端是 34 | 12 | 78 | 56 ;如何区分两个数字与大小端无关,与你用的类型有关。如果你从地址 0 读取两个 int16,那么就是两个数字,如果你从地址 0 读取一个 int32,那么就是一个数字
2. 有,记得数据发送时从高地址位先行,接收端也是从高地址位开始接收。

littlefishcc

littlefishcc   9 小时 18 分钟前

大小端是电脑对 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

xujinkai   8 小时 35 分钟前 via Android

字节流没有大小端。
数字的话,在写入和解析的时候你是知道长度的。你举的例子是两个两字节的数字,所以是第一个。如果是两个四字节的数字,最后就是 0x3412000078560000 。
至于如何确定到底是两字节还是四字节,那是提前约定好的。

ecnelises

ecnelises   8 小时 8 分钟前

大小端的最小单位是字节而不是位,而且针对的是内存操作中的单个数据,比如 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

realradiolover   6 小时 43 分钟前

首先,从网卡、内存、磁盘的角度看,传输、保存的永远都是字节流。给字节流中的字节赋予意义,例如数据类型( 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

x97bgt   6 小时 31 分钟前

@littlefishcc @ecnelises
- 读字节流的顺序都是固定的,但会按大小端的不同来“理解”。
- 写字节流,按怎么顺序写的,也是受大小端影响的。

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

x97bgt

x97bgt   6 小时 24 分钟前

@realradiolover

计算机读写的最小单位是字长( 16 位或 32 位或 64 位),每次都是接受一个字。大小端是在这个字的尺度内读写机制的不同。是这么一回事么?

x97bgt

x97bgt   6 小时 18 分钟前

@realradiolover
假设主机是 0101 1101 1010 0001,那网卡会把它变成 1011 0101 1000 0101 (每个字节内部的 bit 反置),然后送到网络上传输。你说的网卡转换 bit 序,是指这么一回事吗?

这个好像不是我所认识的大小端的定义了。。。。

x97bgt

x97bgt   6 小时 17 分钟前

上面写错了,是 0101 1101 1010 0001 -> 1011 1010 1000 0101

FACEB00K

FACEB00K   46 分钟前

Q: 小端是 34 | 12 | 78 | 56 还是 78 | 56 | 34 | 12
A: 小端是 34 12 78 56. 因为这是两个数字

Q: 大小端如何区分这两个数字
A: 因为是两个数字,所以程序里咱自己会向 buffer 中写两次啊, 收到数据的时候也应该从 buffer 中读两次

Q: 最后的问题
A: 字节流肯定是不会变的,发送什么数据接收到的就是什么数据。但你接受方要读两个字节的数据,现在你只收到一个,不也是要等到第二个收到了,才能构造出 short 类型的数据嘛

Q:什么时候会发生大小端问题
A:发送方大小端不一样,都用的 memcpy 来读写数据。实际开发的时候,定的协议里都会定某个字节是低自节还是高字节,然后可以显示地封装和显示地构造。buff[0] = num & 0xff; buff[1] = (num >> 8) & 0xff;

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK