V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
x97bgt
V2EX  ›  程序员

大小端存储的疑问

  •  
  •   x97bgt · 2021-10-20 15:00:55 +08:00 · 3460 次点击
    这是一个创建于 1129 天前的主题,其中的信息可能已经有所发展或是发生改变。

    基本理解是,字节(十六进制 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?大小端咋区分这两个数字(怎么知道它是两个,而不是一个数字?)
    • 如果是字节流,也有大小端存储之分吗?我的理解是,来一个字节就存一下,似乎没必要分大小端。
    26 条回复    2021-10-21 23:49:32 +08:00
    Mitt
        1
    Mitt  
       2021-10-20 15:06:31 +08:00
    按我的理解大小端也是个约定,跟字节流无关,你写和读的时候才需要区分,存储不需要

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

    另外你说的第二点,也就是,读写前,数字的二进制长度都是已经先确定的了。靠这个保证来区分数字间的间隔?
    Mitt
        4
    Mitt  
       2021-10-20 15:17:54 +08:00
    @x97bgt #3 这是内存布局,没有大小端区别,你从 buffer 读始终是从左到右一位一位读的,另外数字是的,每个数字也就是二进制长度都是固定的,如果不固定常见的作法是前面加一个标识接下来要读多少个字节,而数字对应的编程是 int 有 int32 64 8 等等,int8 就是 byte 也就 8 个 bit,存储的时候没有什么整数浮点区分,都是 bytes
    leoleoasd
        5
    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
        6
    chanchancl  
       2021-10-20 15:19:29 +08:00
    大小端只是你已知数据,该如何进行存储和传送的问题(比如内存、硬盘、网络)
    如何区分数字?存储系统本质上存的都是 01 序列,是不管区分的,区分靠的是你上层的 protocol
    对于字节流,没必要区分,因为计算机存储的最小寻址单位就是字节了
    jorneyr
        7
    jorneyr  
       2021-10-20 15:40:41 +08:00
    怎么知道它是两个,而不是一个数字?
    这个属于解析了,只有在写程序的时候才知道,也就是写程序的时候你很明确的说读取一个 32 位 int 还是读取一个 64 位 int,存储是没有逻辑的,使用才有逻辑,例如 Java 里的 DataInputStream.readInt() 和 DataInputStream.readLong()。
    x97bgt
        8
    x97bgt  
    OP
       2021-10-20 16:01:55 +08:00
    @Mitt @leoleoasd @chanchancl
    「机器读出来」,这句话具体表示什么? 是机器把数据把数据从磁盘里读到内存 /寄存器?

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

    端序体现在『如何把 0x12345678 这个 int 类型数据转换为字节序列』。大端序和小端序转换结果不同。
    x97bgt
        10
    x97bgt  
    OP
       2021-10-20 16:25:33 +08:00
    @leoleoasd
    但我把 int 变成了 byte,然后存到内存 /磁盘里,顺序也会受大小端影响吧?
    dong568789
        11
    dong568789  
       2021-10-20 17:28:22 +08:00
    你理解的没错,int 类型的大端和小端在磁盘上的顺序是不一致的,所以才需要双方协商好编 /解码的 protocol
    araaaa
        12
    araaaa  
       2021-10-20 17:34:39 +08:00
    只有整型才有大小端
    index90
        13
    index90  
       2021-10-20 17:40:04 +08:00
    1. 小端是 34 | 12 | 78 | 56
    index90
        14
    index90  
       2021-10-20 17:47:06 +08:00
    1. 小端是 34 | 12 | 78 | 56 ;如何区分两个数字与大小端无关,与你用的类型有关。如果你从地址 0 读取两个 int16,那么就是两个数字,如果你从地址 0 读取一个 int32,那么就是一个数字
    2. 有,记得数据发送时从高地址位先行,接收端也是从高地址位开始接收。
    leoleoasd
        15
    leoleoasd  
       2021-10-20 18:17:27 +08:00
    @x97bgt #10 对
    littlefishcc
        16
    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
        17
    xujinkai  
       2021-10-20 19:21:59 +08:00 via Android
    字节流没有大小端。
    数字的话,在写入和解析的时候你是知道长度的。你举的例子是两个两字节的数字,所以是第一个。如果是两个四字节的数字,最后就是 0x3412000078560000 。
    至于如何确定到底是两字节还是四字节,那是提前约定好的。
    ecnelises
        18
    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
        19
    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
        20
    x97bgt  
    OP
       2021-10-20 21:25:55 +08:00
    @littlefishcc @ecnelises
    - 读字节流的顺序都是固定的,但会按大小端的不同来“理解”。
    - 写字节流,按怎么顺序写的,也是受大小端影响的。

    不知道我这样理解对不对。
    x97bgt
        21
    x97bgt  
    OP
       2021-10-20 21:32:32 +08:00
    @realradiolover

    计算机读写的最小单位是字长( 16 位或 32 位或 64 位),每次都是接受一个字。大小端是在这个字的尺度内读写机制的不同。是这么一回事么?
    x97bgt
        22
    x97bgt  
    OP
       2021-10-20 21:38:25 +08:00
    @realradiolover
    假设主机是 0101 1101 1010 0001,那网卡会把它变成 1011 0101 1000 0101 (每个字节内部的 bit 反置),然后送到网络上传输。你说的网卡转换 bit 序,是指这么一回事吗?

    这个好像不是我所认识的大小端的定义了。。。。
    x97bgt
        23
    x97bgt  
    OP
       2021-10-20 21:39:34 +08:00
    上面写错了,是 0101 1101 1010 0001 -> 1011 1010 1000 0101
    FACEB00K
        24
    FACEB00K  
       2021-10-21 03:10:06 +08:00
    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;
    msg7086
        25
    msg7086  
       2021-10-21 05:00:26 +08:00
    @x97bgt 网卡应该只吃 dumb 字节流。你发送的时候要自己根据协议把数据转换成字节流,然后接收方根据协议把字节流转回来。
    当然通行的做法还有一律使用网络端序(大端序),免得两端扯皮。
    littlefishcc
        26
    littlefishcc  
       2021-10-21 23:49:32 +08:00
    我看了好多回答,感觉本质为什么产生大小端问题还是没有答出来?
    我们看的书籍都说跟 CPU 有关系,CPU 处理 机器码 是没有语言,没有类型可分,他只人 0 1,更加不存在 int short long 这些所谓类型逻辑,既然跟 CPU 有关系,CPU 执行机器码时候是不关心,所以只有在机器码上一层才会有可能,所以我在上面回答说 汇编层的问题, 汇编进行的操作数才有这个问题。汇编代码也跟 CPU 厂商有关系。intel 与 IBM 汇编代码实现不一样,导致操作数在内存不一样,所以才会出现大小端问题。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5321 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 07:42 · PVG 15:42 · LAX 23:42 · JFK 02:42
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.