大小端存储的疑问 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
x97bgt
V2EX    程序员

大小端存储的疑问

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

    基本理解是,字节(十六进制 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
    cmos
        2
    cmos  
       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
    &nbs;   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     Solana     2649 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 14:54 PVG 22:54 LAX 07:54 JFK 10:54
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86