• 在计算机系统中,数据是如何在内存中存储的?这涉及一个重要概念,即数据的“字节序”。字节序有两种主要的模式:大端(Big-endian)和小端(Little-endian),它们决定了多字节数据(如整数、浮点数)在内存中存储的顺序。字节序影响数据的存取、处理和跨平台兼容性。本文将详细讲解大端和小端的定义、使用场景、字节序的影响、检测与转换。

  • 大端和小端的定义

    在大端模式和小端模式中,多字节数据的存储顺序各不相同。假设一个4字节的整数0x12345678,高位字节为0x12,低位字节为0x78,在两种模式下的存储方式如下:

    1. 大端模式(Big-endian)
    • 定义:大端模式将数据的高位字节存储在内存的低地址处,低位字节存储在内存的高地址处。
    • 示例:4字节整数0x12345678在大端模式中按地址逐字节存放顺序为:
      地址:  0x00   0x01   0x02   0x03
      值:   0x12   0x34   0x56   0x78
      
    1. 小端模式(Little-endian)
    • 定义:小端模式将数据的低位字节存储在内存的低地址处,高位字节存储在内存的高地址处。

    • 示例:同一个4字节整数0x12345678在小端模式中的存储顺序为:

      地址:  0x00   0x01   0x02   0x03
      值:   0x78   0x56   0x34   0x12
      

      大端模式和小端模式的主要区别在于存储顺序不同,它们并不会改变数据本身的数值,只是决定了数据的排列方式。


  • C语言-大端和小端影响的数据类型

    • 在C语言中,大端和小端主要影响多字节数据类型
      • 包括 intlongshortfloatdouble 以及包含多字节字段的结构体(struct)类型,
      • 不影响 char(单字节类型)。
    • char 类型
      • char 类型通常是 1 字节(8位),因此它不受大端或小端的影响。无论是在大端还是小端系统中,1 字节的数据的存储方式都一致。
    • int, long, short, float, double 等类型
      这些数据类型通常占据多个字节,因此会受到字节序的影响。字节序是指在存储多字节数据时,字节的顺序安排方式,主要分为大端(Big-endian)和小端(Little-endian)。
    • int:在大多数系统中,int 通常占用 4 字节。例如,在小端模式下,如果一个 int 值为 0x12345678,则在内存中的存储顺序为:
      地址    值
      0x00    78
      0x01    56
      0x02    34
      0x03    12
      
      而在大端模式下,存储顺序则为:
      地址    值
      0x00    12
      0x01    34
      0x02    56
      0x03    78
      
    • long:在不同系统中,long 型可能占用 4 字节或 8 字节。在小端模式下,如果 long 的值为 0x123456789ABCDEF0,则在 8 字节系统中的存储顺序为:
      地址    值
      0x00    F0
      0x01    DE
      0x02    BC
      0x03    78
      0x04    56
      0x05    34
      0x06    12
      0x07    00
      
      而在大端模式下,存储顺序为:
      地址    值
      0x00    00
      0x01    12
      0x02    34
      0x03    56
      0x04    78
      0x05    BC
      0x06    DE
      0x07    F0
      
    • shortshort 类型通常占用 2 字节。例如,如果 short 值为 0xABCD,在小端模式下的存储顺序为:
      地址    值
      0x00    CD
      0x01    AB
      
      在大端模式下,存储顺序为:
      地址    值
      0x00    AB
      0x01    CD
      
    • float 和 double:浮点数类型在内存中以 IEEE 754 标准格式表示,分别占用 4 字节(float)和 8 字节(double)。例如,float 值 3.14 的 IEEE 754 表示在小端模式下可能存储为:
      地址    值
      0x00    0xC3
      0x01    0xF5
      0x02    0x48
      0x03    0x40
      
      而在大端模式下为:
      地址    值
      0x00    0x40
      0x01    0x48
      0x02    0xF5
      0x03    0xC3
      
    • 结构体 (struct) 类型
    结构体中的多字节字段(例如 `int`、`long`)也会受到字节序的影响。假设我们定义了如下结构体:  
      
    ```c
    struct Example {
      int a;    // 4 字节
      short b;  // 2 字节
      long c;   // 8 字节
    };
    ```
      
    在小端模式下,`Example` 结构体的实例在内存中的布局可能如下所示(假设 `a` 为 0x12345678,`b` 为 0xABCD,`c` 为 0x1122334455667788):  
      
    ```
    地址    值
    0x00    78
    0x01    56
    0x02    34
    0x03    12
    0x04    CD
    0x05    AB
    0x06    00
    0x07    00 // 这2个字节的0是结构体的内存对齐, 参考C语言的结构体内存对齐知识
    0x08    88
    0x09    77
    0x0A    66
    0x0B    55
    0x0C    44
    0x0D    33
    0x0E    22
    0x0F    11
    ```
      
    而在大端模式下,存储顺序则为:  
      
    ```
    地址    值
    0x00    12
    0x01    34
    0x02    56
    0x03    78
    0x04    AB
    0x05    CD
    0x06    00
    0x07    00 // 这2个字节的0是结构体的内存对齐, 参考C语言的结构体内存对齐知识
    0x08    11
    0x09    22
    0x0A    33
    0x0B    44
    0x0C    55
    0x0D    66
    0x0E    77
    0x0F    88
    ```
      
    需要注意的是,结构体还可能有填充字节(Padding)以对齐数据,这些填充位在不同系统间可能有所差异。例如,为了使 `long` 字段在 8 字节对齐,编译器可能会在 `short b` 和 `long c` 之间插入填充字节,从而使结构体的实际大小超过其字段总大小。  
    
    • 指针类型
    指针本身是存储内存地址的变量,因此其存储也取决于系统的字节序。比如在 64 位系统中,一个指针通常占用 8 字节。在小端模式下,假设指针指向的地址为 0x00007FFFDEADBEEF,指针的存储顺序为:  
      
    ```
    地址    值
    0x00    EF
    0x01    BE
    0x02    AD
    0x03    DE
    0x04    FF
    0x05    7F
    0x06    00
    0x07    00
    ```
      
    在大端模式下,存储顺序则为:  
      
    ```
    地址    值
    0x00    00
    0x01    00
    0x02    7F
    0x03    FF
    0x04    DE
    0x05    AD
    0x06    BE
    0x07    EF
    ```
    
    • 数组类型
      • 数组的字节序影响主要取决于数组元素的数据类型:
      • 单字节类型数组
        • char 数组(char arr[])也就是字符串,因为每个元素仅占用 1 字节,因此不受大端或小端的影响。例如,char arr[] = {'a', 'b', 'c'} 在大端和小端系统中的存储顺序是相同的。
      • 多字节类型数组
        • int 数组(int arr[]),每个数组元素是多字节类型,因此它的字节序会在大端和小端系统中有所不同。对于一个 int 数组,在小端系统中,数组中的每个 int 元素都按小端顺序存储(低位字节在低地址);在大端系统中,每个 int 元素按大端顺序存储(高位字节在低地址)。但是,数组的元素顺序不变,只是每个元素的字节内部排列不同。

          示例

           int arr[2] = {0x12345678, 0x9ABCDEF0};
          
        • 在小端系统中,内存布局为:

          地址  |  值
          0x00  |  0x78
          0x01  |  0x56
          0x02  |  0x34
          0x03  |  0x12
          0x04  |  0xF0
          0x05  |  0xDE
          0x06  |  0xBC
          0x07  |  0x9A
          
        • 在大端系统中,内存布局为:

          地址  |  值
          0x00  |  0x12
          0x01  |  0x34
          0x02  |  0x56
          0x03  |  0x78
          0x04  |  0x9A
          0x05  |  0xBC
          0x06  |  0xDE
          0x07  |  0xF0
          

          从这个示例可以看出,数组的元素顺序在内存中是连续的,但元素内部的字节顺序会随系统的字节序而变化。


  • 大端和小端的历史和优缺点

  • 在早期的计算机设计中,数据的存储方式并没有统一的标准。不同的计算机制造商和设计师根据他们的硬件架构和优化需求选择了不同的字节序。例如,IBM 的一些老式系统使用大端字节序,而早期的 Intel x86 处理器采用小端字节序。
  • 大端模式的优点
    • 大端字节序可以使得数据的直观性更强,特别是在手动读取内存时。例如,数字的高位部分在内存中靠近低地址,使得在以十六进制形式查看数据时更易于理解。
    • 在许多领域,尤其是与网络协议和文件格式相关的应用,选择了大端字节序作为默认标准(如网络字节序),以方便调试和数据交换。
  • 小端模式的优点:现代的处理器(如Intel和AMD的处理器)普遍使用小端模式。小端模式的数据存储方式对处理器设计更友好,可以更快地进行字节访问。
    • 简化的数值运算

      小端字节序使得在进行数值运算时更为简便,因为低位字节存储在低地址。对于整数类型的增减操作,如果只需要处理低位字节,CPU 可以直接从内存的低地址开始读取,这样在某些情况下可以提高运算速度。例如,对于加法和减法操作,低位字节的变化更频繁,这使得小端模式在这些操作中可能更高效。

    • 内存地址的线性增大

      在小端模式下,逐渐增加的内存地址对应于数据的逐步扩展,这种线性关系可以使得某些算法在处理数据时更加直观和高效。例如,循环遍历字节数组时,内存地址的顺序与数据的大小顺序一致,便于实现简单的访问逻辑。

    • 与变量的对齐

      小端字节序可以在某些情况下更好地与字节对齐(alignment)结合使用。例如,许多 CPU 在读取对齐的多字节变量时效率更高。对于小端存储,低位字节总是位于低地址,从而使得对齐的访问变得更加简单和高效。

    • 易于扩展

      对于多字节数据结构,例如 intlong,小端模式使得扩展到更大的数据类型(如 long long)变得简单,因为新的高位字节将会被追加在内存的高地址中,而不会影响已有的低位字节。这样,在向后兼容性方面也更容易实现。


  • 为什么按字节存储和访问数据

    内存的每个地址单元通常以字节为单位存储数据。这种按字节存储的方式具有多方面的优势:

    1. 灵活的寻址方式
      按字节编号的地址让数据存储更灵活,并能实现按需存储。例如,单字节、双字节或四字节的数据可以按字节连续存储。无论数据的大小如何,都可以在地址上找到其对应的字节单元。
    2. 便于跨平台兼容
      按字节存储使得数据传输和解释更加灵活。不同字节序的系统只需按字节顺序排列,而不会影响数据内容的完整性。
    3. 方便数据操作和传输
      多字节数据存储成字节块后,可以实现以最小存储单位进行传输和操作,不仅节省资源,也简化了系统设计,提高了存储与访问效率。

  • 为什么一个字节是8位

    在数据存储单位的定义上,1字节=8位(bit)已经成为计算机领域的基本标准,原因如下:

    1. 历史标准化的结果
      在早期的计算机系统中,不同设计曾尝试使用6、7、9位等位数定义字节。但在字符编码和存储设计的标准化进程中,8位逐渐成为共识。这是因为8位提供了256种组合(0-255),可以表示足够丰富的字符集(如ASCII)。

    2. 适合字符编码需求
      8位字节的长度满足了早期字符编码标准(如ASCII码表需要7位来表示128个字符),并允许留出1位进行扩展。8位字节能兼容ISO 8859、UTF-8等编码标准,为不同字符集提供了灵活支持。

    3. 硬件实现简单且有效
      8位是2的整数幂,计算机以2的倍数位进行数据处理更加方便、运算效率更高。8位数据在硬件上更容易实现逻辑操作和内存地址寻址,提升了整体计算效率。


  • 大端小端的检测和转换

    检测系统的字节序并转换数据格式在跨平台编程中十分重要。常用方法包括:

    1. 检测字节序
      可以通过检查一个整数的低位字节来判断系统字节序。例如,在C语言中:
    •  #include <stdio.h>
      
       int main() {
           int num = 1;
           if (*(char*)&num == 1) {
               printf("小端模式\n");
           } else {
               printf("大端模式\n");
           }
           return 0;
       }
      

      通过将整数强制转换为字符指针,可以直接读取第一个字节。如果第一个字节是低位,则系统为小端模式。

      1. 字节序转换
        操作系统提供了字节序转换函数用于网络编程中的标准化字节序转换。例如:
    • htonl(host to network long):将主机字节序转换为网络字节序(大端)

    • ntohl(network to host long):将网络字节序转换为主机字节序(小端)

      这些函数在跨系统数据传输时确保了数据的一致性和兼容性。


  • 总结

  • 大端和小端是多字节数据存储的两种方式。大端模式将高位字节存储在低地址处,小端模式则相反。C语言中,多字节类型如intlongshort和浮点数受字节序影响,而char类型不受影响。字节序的不同影响结构体和数组的内存布局。
  • 理解大端小端、字节序的存储和转换对系统编程、网络通信和文件存储十分关键,有助于构建健壮且兼容性强的计算机应用。
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐