字节序问题--大端法小端法

本文详细介绍了字节序的概念,包括Big-Endian和Little-Endian两种主要类型,并通过实例对比了它们在不同平台上的表现。此外,还提供了测试系统字节序的方法及网络传输中字节序转换的相关知识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、字节序定义

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

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

在所有的介绍字节序的文章中都会提到字节序分为两类:Big-Endian和Little-Endian。引用标准的Big-Endian和Little-Endian的定义如下:
a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
c) 网络字节序:4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。比如,以太网头部中2字节的“以太网帧类型”,表示后面数据的类型。对于ARP请求或应答的以太网帧类型来说,在网络传输时,发送的顺序是0x08,0x06。在内存中的映象如下图所示:
栈底 (高地址)
---------------
0x06 -- 低位 
0x08 -- 高位
---------------
栈顶 (低地址)
该字段的值为0x0806。按照大端方式存放在内存中。

二、高/低地址与高低字节

首先我们要知道我们C程序映像中内存的空间布局情况:在《C专家编程》中或者《Unix环境高级编程》中有关于内存空间布局情况的说明,大致如下图:
----------------------- 最高内存地址 0xffffffff
 | 栈底
 .
 .              栈
 .
  栈顶
-----------------------
 |
 |
\|/

NULL (空洞)

/|\
 |
 |
-----------------------
                堆
-----------------------
未初始化的数据
----------------(统称数据段)
初始化的数据
-----------------------
正文段(代码段)
----------------------- 最低内存地址 0x00000000

以上图为例如果我们在栈上分配一个unsigned char buf[4],那么这个数组变量在栈上是如何布局的呢[注1]?看下图:
栈底 (高地址)
----------
buf[3]
buf[2]
buf[1]
buf[0]
----------
栈顶 (低地址)

现在我们弄清了高低地址,接着来弄清高/低字节,如果我们有一个32位无符号整型0x12345678(呵呵,恰好是把上面的那4个字节buf看成一个整型),那么高位是什么,低位又是什么呢?其实很简单。在十进制中我们都说靠左边的是高位,靠右边的是低位,在其他进制也是如此。就拿0x12345678来说,从高位到低位的字节依次是0x12、0x34、0x56和0x78。

高低地址和高低字节都弄清了。我们再来回顾一下Big-Endian和Little-Endian的定义,并用图示说明两种字节序:
以unsigned int value = 0x12345678为例,分别看看在两种字节序下其存储情况,我们可以用unsigned char buf[4]来表示value:
Big-Endian: 低地址存放高位,如下图:
栈底 (高地址)
---------------
buf[3] (0x78) -- 低位
buf[2] (0x56)
buf[1] (0x34)
buf[0] (0x12) -- 高位
---------------
栈顶 (低地址)

Little-Endian: 低地址存放低位,如下图:
栈底 (高地址)
---------------
buf[3] (0x12) -- 高位
buf[2] (0x34)
buf[1] (0x56)
buf[0] (0x78) -- 低位
---------------
栈顶 (低地址)

在现有的平台上Intel的X86采用的是Little-Endian,而像Sun的SPARC采用的就是Big-Endian。

三、例子

嵌入式系统开发者应该对Little-endian和Big-endian模式非常了解。采用Little-endian模式的CPU对操作数的存放方式是从低字节到高字节,而Big-endian模式对操作数的存放方式是从高字节到低字节。

例如,16bit宽的数0x1234在Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:

内存地址  存放内容
 0x4001    0x12
 0x4000    0x34

而在Big-endian模式CPU内存中的存放方式则为:

内存地址  存放内容
 0x4001    0x34
 0x4000    0x12
 
32bit宽的数0x12345678在Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:

内存地址  存放内容
 0x4003     0x12
 0x4002     0x34
 0x4001     0x56
 0x4000     0x78
 
而在Big-endian模式CPU内存中的存放方式则为:

内存地址  存放内容
 0x4003     0x78
 0x4002     0x56
 0x4001     0x34
 0x4000     0x12

 

 

字节序测试程序

不同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转换成网络字节序,可见网络字节序是大端法。

 

3. 拿来主义

Byte Endian是指字节在内存中的组织,所以也称它为Byte Ordering,或Byte Order。

     对于数据中跨越多个字节的对象, 我们必须为它建立这样的约定:

(1) 它的地址是多少?

(2) 它的字节在内存中是如何组织的?

    针对第一个问题,有这样的解释:

    对于跨越多个字节的对象,一般它所占的字节都是连续的,它的地址等于它所占字节最低地址。(链表可能是个例外, 但链表的地址可看作链表头的地址)。

    比如: int x,它的地址为0x100。 那么它占据了内存中的Ox100, 0x101, 0x102, 0x103这四个字节(32位系统,所以int占用4个字节)。

    上面只是内存字节组织的一种情况: 多字节对象在内存中的组织有一般有两种约定。 考虑一个W位的整数。

    它的各位表达如下:[Xw-1, Xw-2, ... , X1, X0],它的

    MSB (Most Significant Byte, 最高有效字节)为 [Xw-1, Xw-2, ... Xw-8];

    LSB (Least Significant Byte, 最低有效字节)为 [X7,X6,..., X0]。

    其余的字节位于MSB, LSB之间。

 

DEC (Digital Equipment Corporation,现在是Compaq公司的一部分)和Intel的机器(X86平台)一般采用小端。

IBM, Motorola(Power PC), Sun的机器一般采用大端。

当然,这不代表所有情况。有的CPU即能工作于小端, 又能工作于大端,比如ARM, Alpha,摩托罗拉的PowerPC。 具体情形参考处理器手册。

具体这类CPU是大端还是小端,应该和具体设置有关。

(如,Power PC支持little-endian字节序,但在默认配置时是big-endian字节序)

一般来说,大部分用户的操作系统(如windows, FreeBsd,Linux)是Little Endian的。少部分,如MAC OS ,是Big Endian 的。

所以说,Little Endian还是Big Endian与操作系统和芯片类型都有关系。因此在一个处理器系统中,有可能存在大端和小端模式同时存在的现象。这一现象为系统的软硬件设计带来了不小的麻烦,这要求系统设计工程师,必须深入理解大端和小端模式的差别。大端与小端模式的差别体现在一个处理器的寄存器,指令集,系统总线等各个层次中。

 

Linux系统中,你可以在/usr/include/中(包括子目录)查找字符串BYTE_ORDER(或

_BYTE_ORDER, __BYTE_ORDER),确定其值。BYTE_ORDER中文称为字节序。这个值一般在endian.h或machine/endian.h文件中可以找到,有时在feature.h中,不同的操作系统可能有所不同。

 

【用函数判断系统是Big Endian还是Little Endian】

enum {FALSE = 0, TRUE = !FALSE};

typedef short BOOL;

BOOL IsBig_Endian()

//如果字节序为big-endian,返回true;

//反之为   little-endian,返回false

{

    unsigned short test = 0x1122;

    if(*( (unsigned char*) &test ) == 0x11)

       return TRUE;

else

    return FALSE;

 

}//IsBig_Endian()

 

//////////////////////////////////////////////////////////////////////////////

 

可以做个实验

在windows上下如下程序

#include <stdio.h>

#include <assert.h>

 

void main( void )

{

        short test;

        FILE* fp;

        

        test = 0x3132; //(31ASIIC码的’1’,32ASIIC码的’2’)

        if ((fp = fopen ("c:\\test.txt", "wb")) == NULL)

              assert(0);

        fwrite(&test, sizeof(short), 1, fp);

        fclose(fp);

}

    然后在C盘下打开test.txt文件,可以看见内容是21,而test等于0x3132,可以明显的看出来x86的字节顺序是低位在前.如果我们把这段同样的代码放到(big-endian)的机器上执行,那么打出来的文件就是12.这在本机中使用是没有问题的.但当你把这个文件从一个big- endian机器复制到一个little-endian机器上时就出现问题了.

    如上述例子,我们在big-endian的机器上创建了这个test文件,把其复制到little-endian的机器上再用fread读到一个 short里面,我们得到的就不再是0x3132而是0x3231了,这样读到的数据就是错误的,所以在两个字节顺序不一样的机器上传输数据时需要特别小心字节顺序,理解了字节顺序在可以帮助我们写出移植行更高的代码.

正因为有字节顺序的差别,所以在网络传输的时候定义了所有字节顺序相关的数据都使用big-endian,BSD的代码中定义了四个宏来处理:

#define ntohs(n)     //网络字节顺序到主机字节顺序 n代表net, h代表host, s代表short

#define htons(n)     //主机字节顺序到网络字节顺序 n代表net, h代表host, s代表short

#define ntohl(n)      //网络字节顺序到主机字节顺序 n代表net, h代表host, s代表 long

#define htonl(n)      //主机字节顺序到网络字节顺序 n代表net, h代表host, s代表 long

 

举例说明下这其中一个宏的实现:

#define sw16(x) \

    ((short)( \

        (((short)(x) & (short)0x00ffU) << 8) | \

        (((short)(x) & (short)0xff00U) >> 8) ))

这里实现的是一个交换两个字节顺序.其他几个宏类似.

 

我们改写一下上面的程序

#include <stdio.h>

#include <assert.h>

 

#define sw16(x) \

    ((short)( \

        (((short)(x) & (short)0x00ffU) << 8) | \

        (((short)(x) & (short)0xff00U) >> 8) ))

 

#define sw32(x) \

((long)( \

   (((long)(x) & (long)0x000000ff) << 24) | \

   (((long)(x) & (long)0x0000ff00) << 8) | \

   (((long)(x) & (long)0x00ff0000) >> 8) | \

   (((long)(x) & (long)0xff000000) >> 24) ))

 

// 因为x86下面是低位在前,需要交换一下变成网络字节顺序

#define htons(x) sw16(x)

#define htonl(x) sw32(x)

 

void main( void )

{

        short test;

        FILE* fp;

        

        test = htons(0x3132); //(31ASIIC码的’1’,32ASIIC码的’2’)

        if ((fp = fopen ("c:\\test.txt", "wb")) == NULL)

              assert(0);

        fwrite(&test, sizeof(short), 1, fp);

        fclose(fp);

}

    如果在高字节在前的机器上,由于与网络字节顺序一致,所以我们什么都不干就可以了,只需要把#define htons(x) sw16(x)宏替换为 #define htons(x) (x).

    一开始我在理解这个问题时,总在想为什么其他数据不用交换字节顺序?比如说我们write一块buffer到文件,最后终于想明白了,因为都是 unsigned char类型一个字节一个字节的写进去,这个顺序是固定的,不存在字节顺序的问题

 

 

如何判断系统是Big Endian还是Little Endian?

在/usr /include/中(包括子目录)查找字符串BYTE_ORDER(或_BYTE_ORDER, __BYTE_ORDER),确定其值。这个值一般在endian.h或machine/endian.h文件中可以找到,有时在feature.h中,不同的操作系统可能有所不同。一般来说,Little Endian系统BYTE_ORDER(或_BYTE_ORDER,__BYTE_ORDER)为1234,Big Endian系统为4321。大部分用户的操作系统(如windows, FreeBsd,Linux)是Little Endian的。少部分,如MAC OS ,是Big Endian 的。本质上说,Little Endian还是Big Endian与操作系统和芯片类型都有关系。

 

Processor OS Order

x86 (Intel, AMD, … ) All little-endian

DEC Alpha All little-endian

HP-PA NT little-endian

HP-PA UNIX big-endian

SUN SPARC All? big-endian

MIPS NT little-endian

MIPS UNIX big-endian

PowerPC NT little-endian

PowerPC non-NT big-endian

RS/6000 UNIX big-endian

Motorola m68k All big-endian


<think>好的,我现在需要帮助用户了解C++中整型数据的大端小端存储方式。首先,我需要回顾用户提供的引用内容,看看哪些信息可以直接使用。引用1、3、4、5都提到了大小端的检测方,特别是通过将整数1的字节表示来判断。用户之前的问题也涉及整型的存储,所以需要结合这些内容。首先,我应该解释什么是大端小端存储。根据引用5,小端是高位存在高位内存,而大端是高位存在低位内存,符合人类习惯。需要用例子说明,比如整数0x12345678的存储方式。接下来,用户可能想知道如何在C++中检测大小端。引用1、3、4中的代码示例展示了如何通过将int转换为char指针来检查第一个字节是否为1。我需要将这些方整合成一个清晰的步骤,并给出代码示例。另外,用户提到引用2中的转换函数,可能还想知道如何转换字节顺序。这部分需要解释转换的原理,比如使用位移和掩码操作,或者使用库函数如htonl和ntohl。还需要注意数据对齐的问题,引用5提到数据对齐的原因,但用户的问题主要集中在大小端,所以可能不需要深入讨论对齐,但可以简要提及。用户可能的应用场景包括网络编程和跨平台数据传输,这时候处理字节序很重要。需要举例说明这些场景下的最佳实践,比如使用网络字节序大端)和转换函数。最后,生成相关的问题,比如检测方、转换方网络编程中的应用,以及不同架构的影响。确保问题覆盖用户可能进一步探索的方向。在写作时,要确保结构清晰,分点解释,代码示例正确,并且引用标记正确。同时,要使用中文,避免使用Markdown格式,但按照用户的要求,最终回答需要使用特定的格式,包括引用标识和代码块。需要检查是否有遗漏的引用,并正确标注。 可能还需要指出,C++标准本身没有规定字节序,所以程序不应该依赖特定字节序,除非处理外部数据。这时,转换函数如引用2中的模板函数就派上用场了。需要解释这个模板函数的工作原理,如何将任意整型转换为小端存储。总结,回答的结构应该是:定义大小端、检测方、转换方、应用场景和注意事项,最后相关问题。确保每个部分都有引用支持,并自然添加引用标记。</think>### C++整型数据的大端/小端存储方式详解 --- #### 一、基本概念 1. **大端序(Big-Endian)** - **高位字节存储在低地址**,符合人类阅读习惯 - 示例:数值 `0x12345678` 存储为 `12 34 56 78`(从左到右地址递增)[^5] 2. **小端序(Little-Endian)** - **低位字节存储在低地址**,Intel架构常用 - 示例:数值 `0x12345678` 存储为 `78 56 34 12`[^5] --- #### 二、检测方 1. **指针检测** ```cpp bool is_little_endian() { int num = 1; return *(reinterpret_cast<char*>(&num)) == 1; // 取第一个字节判断 } ``` - 原理:小端序中最低有效字节存储在最低地址[^1][^3] 2. **联合体检测** ```cpp union EndianChecker { int value; char bytes[4]; }; bool check_endian() { EndianChecker ec{1}; return ec.bytes[0] == 1; // 小端返回true } ``` - 利用联合体共享内存空间的特性[^4] --- #### 三、字节序转换 1. **手动转换模板** ```cpp template <typename T> T to_big_endian(T value) { size_t size = sizeof(T); T result = 0; for (size_t i = 0; i < size; ++i) { result |= ((value >> (8 * i)) & 0xFF) << (8 * (size - 1 - i)); } return result; } ``` - 将任意整型转换为大端序[^2] 2. **系统函数** - 使用 `htonl()`(32位)、`htons()`(16位)等网络字节序函数 ```cpp uint32_t network_order = htonl(host_order); // 主机序→网络序(大端) ``` --- #### 四、应用场景 1. **网络通信** - 网络协议默认使用大端序,需转换本地数据[^2] 2. **文件格式解析** - 如JPEG文件头使用大端序存储元数据 3. **跨平台开发** - 不同架构设备(ARM/Intel)间数据交换时需统一字节序 --- #### 五、注意事项 1. **不可移植性** ```cpp // 错误示例:直接读取二进制内存 int read_value = *reinterpret_cast<int*>(network_buffer); // 可能因字节序错误 ``` 2. **数据对齐问题** -平台传输时需考虑结构体填充(使用 `#pragma pack` 控制对齐)[^5] --- #### 示例代码 ```cpp #include <iostream> #include <arpa/inet.h> // 包含htonl等函数 int main() { uint32_t host_value = 0x12345678; uint32_t network_value = htonl(host_value); std::cout << "主机序: 0x" << std::hex << host_value << "\n" << "网络序: 0x" << network_value << std::endl; return 0; } ``` ---
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值