大端法、小端法、网络字节序 转

本文深入探讨了字节序(大端法与小端法)的概念及其在网络传输中的作用,通过实例展示了如何在不同平台间进行字节序转换,并提供了一个简单的C程序用于测试不同平台上的字节序。重点分析了80X86平台与网络字节序之间的区别,总结了两者在实际应用中的关键点。
转自:http://www.blogjava.net/tinysun/archive/2009/12/31/307952.html?1356194245

关于字节序(大端法、小端法)的定义
《UNXI网络编程》定义:术语“小端”和“大端”表示多字节值的哪一端(小端或大端)存储在该值的起始地址。小端存在起始地址,即是小端字节序;大端存在起始地址,即是大端字节序。

 

也可以说: 
1.小端法(Little-Endian)就是低位字节排放在内存的低地址端即该值的起始地址,高位字节排放在内存的高地址端。 
2.大端法(Big-Endian)就是高位字节排放在内存的低地址端即该值的起始地址,低位字节排放在内存的高地址端。

举个简单的例子,对于整形0x12345678。它在大端法和小端法的系统内中,分别如图1所示的方式存放。

zijiexu_pic_1 

 

网络字节序

我们知道网络上的数据流是字节流,对于一个多字节数值,在进行网络传输的时候,先传递哪个字节?也就是说,当接收端收到第一个字节的时候,它是将这个字节作为高位还是低位来处理呢? 
网络字节序定义:收到的第一个字节被当作高位看待,这就要求发送端发送的第一个字节应当是高位。而在发送端发送数据时,发送的第一个字节是该数字在内存中起始地址对应的字节。可见多字节数值在发送前,在内存中数值应该以大端法存放。
网络字节序说是大端字节序。 
比如我们经过网络发送0x12345678这个整形,在80X86平台中,它是以小端法存放的,在发送前需要使用系统提供的htonl将其转换成大端法存放,如图2所示。

zijiexu_pic_2

 

字节序测试程序 
不同cpu平台上字节序通常也不一样,下面写个简单的C程序,它可以测试不同平台上的字节序。
#include <stdio.h>
#include <netinet/in.h>
int main()
{
    int i_num = 0x12345678;
    printf("[0]:0x%x\n", *((char *)&i_num + 0));
    printf("[1]:0x%x\n", *((char *)&i_num + 1));
    printf("[2]:0x%x\n", *((char *)&i_num + 2));
    printf("[3]:0x%x\n", *((char *)&i_num + 3));
10   
11      i_num = htonl(i_num);
12      printf("[0]:0x%x\n", *((char *)&i_num + 0));
13      printf("[1]:0x%x\n", *((char *)&i_num + 1));
14      printf("[2]:0x%x\n", *((char *)&i_num + 2));
15      printf("[3]:0x%x\n", *((char *)&i_num + 3));
16   
17      return 0;
18  } 


在80X86CPU平台上,执行该程序得到如下结果: 
[0]:0x78 
[1]:0x56 
[2]:0x34 
[3]:0x12

[0]:0x12 
[1]:0x34 
[2]:0x56 
[3]:0x78

分析结果,在80X86平台上,系统将多字节中的低位存储在变量起始地址,使用小端法。htonl将i_num转换成网络字节序,可见网络字节序是大端法。

总结点:80X86使用小端法,网络字节序使用大端法。

<think> 我们正在讨论C语言中的联合(union)以及字节序大端序和小端序)的问题。用户的问题是关于联合类型中成员共享内存的特性,以及为什么给整数成员赋值后,通过浮点数成员读取不能得到对应的浮点数值。同时,用户还要求解释大端序、小端序以及浮点数读取解析。 根据之前的讨论,我们已经知道联合的所有成员共享同一块内存区域,因此一个成员的赋值会影响其他成员的值,但不同数据类型的二进制表示是不同的。整数和浮点数在内存中的存储方式完全不同,即使它们占用的字节数相同(例如int和float通常都是4字节),但它们的位模式解释方式不同。 用户现在要求详细解释大端序、小端序以及浮点数读取解析。下面将分步骤解释: ### 1. 大端序和小端字节序(Endianness)是指多字节数据存储在内存中的顺序。它分为两种: - **大端序(Big-Endian)**:数据的最高有效字节(Most Significant Byte, MSB)存储在最低的内存地址处。 - **小端序(Little-Endian)**:数据的最低有效字节(Least Significant Byte, LSB)存储在最低的内存地址处。 例如,整数`0x12345678`(4字节)在不同字节序下的存储方式: - 大端序(地址由低到高):`0x12`、`0x34`、`0x56`、`0x78` - 小端序(地址由低到高):`0x78`、`0x56`、`0x34`、`0x12` 在C语言中,可以使用以下代码检测当前系统的字节序: ```c #include <stdio.h> int main() { int n = 1; if (*(char *)&n == 1) { printf("小端序\n"); } else { printf("大端序\n"); } return 0; } ``` [^4] ### 2. 浮点数的存储 浮点数通常遵循IEEE 754标准进行存储。单精度浮点数(float)占4字节(32位),分为三个部分: - 符号位(1位):0表示正数,1表示负数。 - 指数部分(8位):采用偏移码(Excess-127)表示。 - 尾数部分(23位):归一化后的二进制小数部分。 例如,浮点数`99.0`的二进制表示: 1. 转换为二进制科学计数:`99.0 = 1.100011 × 2^6`(因为99的二进制是1100011,所以归一化为1.100011乘以2的6次方)。 2. 指数部分:实际指数为6,加上偏移127,得到133(二进制为10000101)。 3. 尾数部分:100011(后面补0至23位)。 4. 符号位为0(正数)。 5. 组合:符号位(0)+指数(10000101)+尾数(10001100000000000000000)即二进制:`01000010 11000110 00000000 00000000`,十六进制为`0x42C60000`。 ### 3. 联合中整数和浮点数的读取解析 在联合`union data`中,成员`d1`(int)和`d2`(float)共享同一块内存(4字节)。当我们给`d1`赋值整数99(十六进制为`0x00000063`,注意字节序会影响内存布局)后,内存中存储的是99的二进制表示。 - 在小端序系统中,内存布局(地址由低到高)为:`0x63`、`0x00`、`0x00`、`0x00`。 - 在大端序系统中,内存布局为:`0x00`、`0x00`、`0x00`、`0x63`。 然后,我们通过`d2`读取这块内存,将其解释为IEEE 754浮点数: - 在小端序系统中,内存中的字节序列(`0x63`、`0x00`、`0x00`、`0x00`)实际上是`0x00000063`(因为小端序中,第一个字节是最低字节,所以整个32位整数是0x63000000?不对,重新整理一下:小端序下,内存从低到高是0x63,0x00,0x00,0x00,那么整个32位就是0x00000063?不对,应该是:最低地址是0x63,然后是0x00,0x00,0x00,所以整个32位数是0x00000063?不对,实际上小端序的规则是:最低地址存放最低有效字节,所以四个字节从低到高是:第一个字节(地址最低)是最低字节(LSB),最后一个字节是最高字节(MSB)。因此,对于整数99(十六进制0x00000063,因为99十进制=0x63十六进制,而0x63只需要一个字节,所以扩展成4字节就是0x00000063): - 小端序:低地址存0x63,然后0x00, 0x00, 0x00(因为0x63是最低字节)。 - 大端序:低地址存0x00,然后0x00, 0x00, 0x63(因为0x63是最高字节?不对,实际上0x00000063的最高字节是0x00,然后是0x00, 0x00,最低字节是0x63。所以大端序:0x00, 0x00, 0x00, 0x63;小端序:0x63,0x00,0x00,0x00)。 所以,当我们通过浮点数成员`d2`读取这块内存时,无论字节序如何,内存中的位模式都是`0x00000063`(大端序)或`0x63000000`(小端序)?不对,实际上: - 在小端序系统中,内存中的四个字节(从低地址到高地址)分别是0x63, 0x00, 0x00, 0x00,所以整个32位的值按小端序重新组合就是0x00000063(因为最低字节0x63放在最低8位,然后三个0字节依次放在高位)。但是,当我们用浮点数读取时,并不进行重新组合,而是直接按字节顺序解释为IEEE 754浮点数。IEEE 754标准没有规定字节序,因此它依赖于硬件的字节序。也就是说,在内存中,浮点数的各个字节的排列顺序也遵循同样的字节序。 因此,在小端序机器上,浮点数同样按照小端序存储。所以,当我们把整数99(小端序存储为:0x63,0x00,0x00,0x00)用浮点数读取时,相当于读取了一个32位的位模式:0x00000063(按内存地址从低到高排列的字节序列对应浮点数的32位,即浮点数的32位中,最低8位是0x63,然后是三个0x00)。这个位模式按照IEEE 754标准解释: - 符号位:0(正数) - 指数部分:0(因为8位指数是0,实际指数是-127) - 尾数部分:0x000063(23位尾数,但这里只有最低8位有值0x63,其余为0) 所以,这是一个非常小的数(接近0),具体值为:$ (1.0 + 0.00000000000000001101011)_2 \times 2^{-127} $,这是一个非规格化的数,非常小,不是99.0。 同样的,在大端序机器上,整数99存储为:0x00,0x00,0x00,0x63(从低地址到高地址)。用浮点数读取时,位模式是0x00000063(大端序,即最高字节0x00,最低字节0x63),但按照大端序的浮点数解释,这个位模式同样不是99.0,因为99.0的位模式是0x42C60000(大端序)。 因此,无论字节序如何,直接将整数99的内存表示解释为浮点数,都不会得到99.0。 ### 4. 为什么选项D是错误的 根据上面的分析,给`demo.d1`赋值99后,`demo.d2`读取的值并不是99.0,而是根据内存中的位模式解释为一个浮点数,这个浮点数的值与99.0在浮点数中的位模式完全不同。所以选项D错误。 ### 回顾其他选项 - **A**:变量demo与成员d2所占的内存字节数相同。因为联合的大小由最大的成员决定,这里int和float都是4字节(假设),所以整个联合demo的大小也是4字节,与d2相同。正确。 - **B**:变量demo中各成员的地址相同。联合的成员共享内存,所以它们的起始地址相同(即整个联合的地址)。正确。 - **C**:变量demo和各成员的地址相同。联合变量的地址就是整个联合的起始地址,也是其成员的地址(因为成员从联合的起始地址开始)。正确。 ### 相关问题 1. 在联合类型中,如果同时存在一个整型和一个字符数组,如何利用该联合检测系统的字节序? 2. 为什么IEEE 754浮点数的指数部分要采用偏移码(Excess-127)表示? 3. 在C语言中,如何安全地进行类型转换(如整数浮点数)而不产生未定义行为? 4. 网络编程中,为什么需要处理字节序转换?常用的转换函数有哪些? </think> ### 大端序、小端序与浮点数读取解析 #### 一、字节序(Endianness)基本概念 字节序指多字节数据在内存中的存储顺序,分为两种类型: 1. **大端序(Big-Endian)** - 最高有效字节(MSB)存储在最低内存地址 - 示例:整数 `0x12345678` 的存储(地址由低→高) $$ \text{地址0: }12 \quad \text{地址1: }34 \quad \text{地址2: }56 \quad \text{地址3: }78 $$ *符合人类阅读习惯,网络传输标准字节序[^2]* 2. **小端序(Little-Endian)** - 最低有效字节(LSB)存储在最低内存地址 - 示例:同一整数 `0x12345678` 的存储 $$ \text{地址0: }78 \quad \text{地址1: }56 \quad \text{地址2: }34 \quad \text{地址3: }12 $$ *x86/x64架构常用,内存访问更高效[^4]* #### 二、字节序检测方(C语言) ```c #include <stdio.h> int main() { int n = 1; // 十六进制: 0x00000001 if (*(char*)&n == 1) // 检查首字节值 printf("小端序\n"); // 首字节=0x01 → LSB在前 else printf("大端序\n"); // 首字节=0x00 → MSB在前 return 0; } ``` *原理:通过类型转换直接访问内存首字节[^4]* #### 三、浮点数存储标准(IEEE 754) 单精度浮点数(float)占4字节(32位),结构为: $$ \underbrace{\text{1位}}_{\text{符号}} \mid \underbrace{\text{8位}}_{\text{指数}} \mid \underbrace{\text{23位}}_{\text{尾数}} $$ - **符号位**:0正1负 - **指数部分**:实际指数 = 存储值 - 127(偏移码) - **尾数部分**:归一化后的二进制小数(隐含前导1) 示例:`99.0` 的存储过程 1. 为二进制科学计数: $$ 99.0 = 1.100011 \times 2^6 \quad (\text{尾数}=100011) $$ 2. 计算指数: $$ \text{指数存储值} = 6 + 127 = 133 = \text{10000101}_\text{b} $$ 3. 完整32位表示: $$ 0\ \underbrace{10000101}_{\text{指数}}\ \underbrace{10001100000000000000000}_{\text{尾数}} $$ 4. 十六进制:`0x42C60000`[^4] #### 四、联合(union)的内存解析 通过联合演示整数与浮点数的内存共享: ```c union Data { int d1; // 4字节整数 float d2; // 4字节浮点数 } demo; ``` 1. **赋值整数后读取浮点数的过程** - 小端序系统执行 `demo.d1 = 99`: $$ \text{内存布局(低→高): } \text{0x63} \mid \text{0x00} \mid \text{0x00} \mid \text{0x00} $$ - 通过 `demo.d2` 读取时,内存被解释为IEEE 754浮点数: $$ \text{二进制: } 0\ \underbrace{00000000}_{\text{指数=0}}\ \underbrace{00000000000000001100011}_{\text{尾数}} $$ - 计算结果: $$ \text{值} = (-1)^0 \times 2^{(0-127)} \times 1.00000000000000001100011_2 \approx 1.4 \times 10^{-45} $$ *99的整数存储(0x00000063)与浮点存储(0x42C60000)二进制完全不同* #### 五、字节序对数据解析的影响 | 操作 | 大端序系统 | 小端序系统 | |---------------------|--------------------|--------------------| | `demo.d1 = 99` | 00 00 00 63 | 63 00 00 00 | | `demo.d2` 读取值 | $3.4 \times 10^{-37}$ | $1.4 \times 10^{-45}$ | | 正确获取99.0的方 | `demo.d2 = (float)demo.d1` | 显式类型转换[^1] | > **关键结论**:字节序决定内存布局,数据类型决定二进制解释。整数与浮点数的存储机制本质不同,直接内存共享会得到无效值。 --- ### 相关问题 1. 网络编程中如何处理不同主机的字节序差异? 2. 为什么IEEE 754浮点数标准使用偏移码表示指数? 3. 如何用联合(union)检测系统的浮点数精度? 4. RISC-V架构默认使用哪种字节序?其设计优势是什么?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值