字节序问题:大端法、小端法

创建时间:2022/1/11 20:20
更新时间:2022/5/10 16:38
作者:gi51wa2j
标签:bingo, 计算机原理, 细节知识


一、字节序定义

   字节序,顾名思义字节的顺序,再多说两句就是大于一个字节类型的数据在内存中的存放顺序(一个字节的数据当然就无需谈顺序的问题了)。

   其实大部分人在实际的开发中都很少会直接和字节序打交道。唯有在跨平台以及网络程序中字节序才是一个应该被考虑的问题。

   在所有的介绍字节序的文章中都会提到字节序分为两类:Big-Endian(大端)Little-Endian(小端)

1.1 Big-Endian

大端模式(Big-endian),是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中。这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;

1.2 Little-Endian

就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
小端模式(Little-endian),是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中


比如:0x12345678,在大/小端模式的存储位置如下:

内存地址

大端模式

小端模式

addr+3

0x78

0x12

addr+2

0x56

0x34

addr+1

0x34

0x56

addr

0x12

0x78

二、C程序的存储空间布局(如何区分高低地址)

历史沿袭至今,C程序一直由下列几部分组成:

2.1 正文段(.text)

      这是由CPU执行的机器指令部分。通常,正文段是可共享的,所以即使是频繁执行的程序(如文本编辑器、C编译器和shell等)在存储器中也只需有一个副本,另外,正文段常常是只读的,心防止程序由于意外而修改其指令。

2.2 初始化数据段(.data)

      通常将此段称为数据段,它包含了程序中需明确地赋初值的变量(已经初始化的非零全局变量)。例如,C程序中任何函数之外的声明:

int maxcount = 99;

使此变量以其初值存放在初始化数据段中。

2.3 未初始化数据段(.bss)

      通常将此段称为bss段,这一名称来源于时期汇编程序的一个操作符,意思是“由符号开始的块“(block started by symbol),在程序开始执行之前,(通常)内核将此段中的数据初始化为0或空指针。存放程序中未初始化的和零值全局变量。函数外的声明:

long sum[1000];

使此变量存放在非初始化数据段中。

text和data段都在可执行文件中,由系统从可执行文件中加载;而bss段不在可执行文件中,由系统初始化。

2.4 栈(stack)

      按内存地址由高到低方向生长,其最大大小由编译时确定。自动变量以及每次函数调用时所需保存的信息都存放在此段中。每次函数调用时,其返回地址以及调用者的环境信息(如某些机器寄存器的值)都存放在栈中。然后,最近被调用的函数在栈上为其自动和临时变量分配存储空间。通过心这种方式使用栈,C递归函数可以工作。递归函数每次调用自身时,就用一个新的栈帧,因此一次函数调用实例中的变量集不会影响另一次函数调用实例中的变量。

2.5 堆(heap)

      自由申请的空间,按内存地址由低到高方向生长,其大小由系统内存/虚拟内存上限决定。通常在堆中进行动态存储分配。由于历史上形成的惯例,堆位于未初始化数据段和栈之间。

 每个线程都会有自己的栈,但是堆空间是共用的。

     

      上面图示中显示了这些段的一种典型安排方式。这是程序的逻辑布局,虽然并不要求一个具体实现一定以这种方式安排其存储空间,但这是一种我们便于说明的典型安排。对于32位Intel x86处理器上的Linux,正文段从0x0804 8000单元开始,栈底则在0xC000 0000之下开始(在这种特定结构中,栈从高地址向低地址方向增长)。堆顶和栈顶之间未用的虚地址空间很大。

  a.out中还有基于其他类型的段,如包含符号表的段、包含调试信息的段以及包含动态共享库链接表的段等。这些部分并不装载到进程执行的程序映像中。

从上图中还可以注意到,未初始化数据段的内容并不存放在磁盘程序文件中。其原因是,内核在程序开始运行前将它们都设置为0。需要存放在磁盘程序文件中的段只有正文段和初始化数据段。

  size(1)命令报告正文段、数据段和bss段的长度(以字节为单位)。例如:

 

这个例子是在Ubuntu x86_64上进行的,其中第4列和第5列是分别民以十进制和十六进制表示的3段总长度。


三、为什么要采用大端和小端

       这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于 8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。例如一个16bit的short型x,在内存中的地址为0x0010,x的值为0x1122,那么0x11为高字节,0x22为低字节。对于大端模式,就将0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。小端模式,刚好相反。我们常用的X86结构是小端模式,STM32是小端模式,而KEIL C51则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。