大端序,小端序问题

转:http://blog.youkuaiyun.com/zephyr_be_brave/article/details/8847319

Big-endian和little-endian是描述排列存储在计算机内存里的字节序列的术语。

Big-endian是一种大值的一端(序列中更典型值)存在前面(在最小的存储地址)的顺序。Little-endian是一种小值的一端(序列中较不典型的值)存储在前的顺序。比如,在Big-endian的电脑中,需要两个字节把十六位数4F52当作4F52存在存储器中(如果4F存在存储地址1000中,比如说,52将存在1001中)。在little-endian系统里,将被存为524F(52存在存储地址1000中,比如说,4F将存在1001中)。


一个例子: 
如果我们将0x1234abcd写入到以0x0000开始的内存中,则结果为 
           big-endian   little-endian 
0x0000      0x12        0xcd 
0x0001      0x34        0xab 
0x0002      0xab        0x34 
0x0003      0xcd        0x12 


嵌入式系统开发者应该对Little-endian和Big-endian模式非常了解。采用Little-endian模式的CPU对操作数的存放方式是从低字节到高字节,而Big-endian模式对操作数的存放方式是从高字节到低字节。也就是说Big-endian模式符合人的习惯,而Little-endian更加方便计算机操作。

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

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

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

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

若判断处理器是Big还是Little模式,有两种方法。

1、

[cpp]  view plain copy
  1. int i=1;     
  2.     char *p=(char *)&i;     
  3.     if(*p==1)       
  4.            printf("Little Endian");    
  5.     else  
  6.            printf("Big Endian");  

          大小端存储问题,如果小端方式(i占至少两个字节的长度)则i所分配的内存最小地址那个字节中就存着1,其他字节是0.大端的话则1在i的最高地址字节处存放,char是一个字节,所以强制将char型量p指向i则p指向的一定是i的最低地址,那么就可以判断p中的值是不是1来确定是不是小端

2、

[cpp]  view plain copy
  1. int isLittleEndian( )  
  2. {  
  3.     {  
  4.            union w  
  5.            {    
  6.                   int a;  
  7.                   char b;  
  8.            } c;  
  9.            c.a = 1;  
  10.            return(c.b ==1);  
  11.     }  
  12. }  

这个解法涉及到Union的内存分配模式,联合体union的存放顺序是所有成员都从低地址开始存放。

Union的大小为其内部所有变量的最大值,并且按照类型最大值的整数倍进行内存对齐。

例如:

[cpp]  view plain copy
  1. typedef Union   
  2. {  
  3.     char c[10];  
  4.     char cc1;  
  5. }u11;  
首先按照char c[10]分配10个字节,然后按照char的1个字节对齐,最终sizeof(u11)=10;

[cpp]  view plain copy
  1. typedef union   
  2. {  
  3.     char c[10];  
  4.     int i;  
  5. }u22;  
首先按照char c[10]分配10个字节,然后按照int的4个字节对齐,最终sizeof(u22)=12;

[cpp]  view plain copy
  1. typedef union   
  2. {  
  3.     char c[10];  
  4.     double d;  
  5. }u33;  
首先按照char c[10]分配10个字节,然后按照double的8个自己对齐,最终sizeof=16;

[cpp]  view plain copy
  1. union U1  {  
  2.      char  c;  
  3.      int  i;  
  4.      double  d;  
  5. } ;  

因此,举例中union分配的内存按照int分配4个字节,如果是小端模式则存放的方式为                                                                                                       

地址A
------------------------------------
|A       |A+1   |A+2    |A+3 | int a;
|0x01 |0x00   |0x00   |0x00 |
-------------------------------------
|A      |char b;
|          |
---------         

如果是大端如何存储c.a的呢?    

地址A

------------------------------------------
|A          |A+1    |A+2      |A+3      |int a;
|0x00   |0x00   |0x00    |0x01    |
------------------------------------------
|A       |char b;
|            |
---------      

因此我们就可以通过查看char b==1?来判断大小端了。

顺便说明一下struct的内存分配方式。

        struct的内存大小为每个数据内存的加和,首先按照最大的数据类型进行单个分配,如果前一个数据占用不了所有的内存,而剩下的内存可以放下下一个数据,则第二个数据不另外分配内存,否则重新分配一个最大类型的内存单元。

[cpp]  view plain copy
  1. struct{  
  2.     char c;  
  3.     double d;  
  4. };//<span style="color:rgb(69,69,69); font-family:tahoma,helvetica,arial; font-size:14px; line-height:21px; background-color:rgb(252,249,244)">16</span>  

[cpp]  view plain copy
  1. struct{  
  2.     char c;  
  3.     char c1;  
  4.     double d;  
  5. };//16  


[cpp]  view plain copy
  1. struct{  
  2.     char c;  
  3.     double d;  
  4.     char c2;  
  5. };24  

Union如何使用?
对联合变量的赋值,使用都只能是对变量的成员进行。联合变量的成员表示为: 
联合变量名.成员名 
例如,a被说明为test类型的变量之后,可使用a.class、a.office 
不允许只用联合变量名作赋值或其它操作,也不允许对联合变量作初始化赋值,赋值只能在程序中进行。
还要再强调说明的是,一个联合变量,每次只能赋予一个成员值。换句话说,一个联合变量的值就是联合变员的某一个成员值。

6、匿名联合
匿名联合仅仅通知编译器它的成员变量共同享一个地址,而变量本身是直接引用的,不使用通常的点号运算符语法.

例如:
#i nclude <iostream>
void main()
{
union{ 
int test;
char c; 
}; 
test=5;
c=′a′;
std::cout<<i<<" "<<c;
}


正如所见到的,联合成分象声明的普通局部变量那样被引用,事实上对于程序而言,这也正是使用这些变量的方式.另外,尽管被定义在一个联合声明中,他们与同一个程序快那的任何其他局部变量具有相同的作用域级别.这意味这匿名联合内的成员的名称不能与同一个作用域内的其他一直标志符冲突.
对匿名联合还存在如下限制:
因为匿名联合不使用点运算符,所以包含在匿名联合内的元素必须是数据,不允许有成员函数,也不能包含私有或受保护的成员。还有,全局匿名联合必须是静态(static)的,否则就必须放在匿名名字空间中。


7、几点需要讨论的地方:
1、联合里面那些东西不能存放?
我们知道,联合里面的东西共享内存,所以静态、引用都不能用,因为他们不可能共享内存。


2、类可以放入联合吗?


我们先看一个例子:
class Test
{
public:
Test():data(0) { }
private:
int data;
};
typedef union _test
{
Test test; 
}UI; 
编译通不过,为什么呢?
因为联合里不允许存放带有构造函数、析够函数、复制拷贝操作符等的类,因为他们共享内存,编译器无法保证这些对象不被破坏,也无法保证离开时调用析够函数。

3、又是匿名惹的祸??


我们先看下一段代码: 
class test
{
public:
test(const char* p);
test(int in);
const operator char*() const {return
data.ch;}
operator long() const {return data.l;}
private:
enum type {Int, String };
union 
{
const char* ch;
int i;
}datatype;
type stype;
test(test&);
test& operator=(const test&);
};
test::test(const char *p):stype
(String),datatype.ch(p) { }
test::test(int in):stype(Int),datatype.l(i) {
}
看出什么问题了吗?呵呵,编译通不过。为什么呢?难道datatype.ch(p)和datatype.l(i)有问题吗?
哈哈,问题在哪呢?让我们来看看构造test对象时发生了什么,当创建test对象时,自然要调用其相应的构造函数,在构造函数中当然要调用其成员的构造函数,所以其要去调用datatype成员的构造函数,但是他没有构造函数可调用,所以出
错。


注意了,这里可并不是匿名联合!因为它后面紧跟了个data!


4、如何有效的防止访问出错?


使用联合可以节省内存空间,但是也有一定的风险:通过一个不适当的数据成员获取当前对象的值!例如上面的ch、i交错访问。
为了防止这样的错误,我们必须定义一个额外的对象,来跟踪当前被存储在联合中的值得类型,我们称这个额外的对象为:union的判别式。
一个比较好的经验是,在处理作为类成员的union对象时,为所有union数据类型提供一组访问函数。


对齐方式

编译器中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。

<think>嗯,用户现在明确要求理解大端小端序的概念及区别,而且提供了几个站内引用作为充材料。从对话历史看,用户之前问过BOM(字节顺标记)的相关问题,现在转向底层字节,可能是想深入理解计算机如何处理多字节数据。 用户提供的引用材料很有价值:[^1]用Verilog示例展示内存布局,[^2]给出C语言端转换代码,[^3][^4]则从网络和系统角度解释概念。需要把这些零散信息整合成连贯的体系。 用户身份可能是程员或计算机专业学生——因为引用涉及代码和硬件内存布局。深层需求可能是解决实际编程中的字节问题,比如网络传输或文件解析。需要强调应用场景,避免纯理论。 核心任务:用生活化类比解释抽象概念(比如地址高低位),用可视化内存布局图辅助理解,最后通过检测代码和转换代码落地到实践。要特别注意和之前BOM知识的关联(比如BOM本质就是解决字节问题的标记)。 需要规避的陷阱:1)避免过度深入硬件电路;2)区分字节和位(用户引用[^1]的data[7:0]容易混淆);3)明确网络字节固定为大端的特殊性。 数学表达要求严格:所有公式用$...$包裹,比如端转换函数中的位运算 $\text{0xFF000000}$ 必须规范书写。检测代码段用独立格式呈现。</think>### 大端小端序详解 #### 一、核心定义 1. **大端 (Big-endian)** 高位字节存储在**低内存地址**,低位字节存储在高地址。 $\text{示例:0x12345678 在内存中的存储}$ | 地址 | 数据 | |------|------| | 低地址 | `12` (最高位字节) | | | `34` | | | `56` | | 高地址 | `78` (最低位字节) 符合人类阅读习惯(从左到右由高到低)[^3]。 2. **小端序 (Little-endian)** 低位字节存储在**低内存地址**,高位字节存储在高地址。 $\text{示例:0x12345678 在内存中的存储}$ | 地址 | 数据 | |------|------| | 低地址 | `78` (最低位字节) | | | `56` | | | `34` | | 高地址 | `12` (最高位字节) 符合计算机处理习惯(先处理低位数据)[^1]。 #### 二、核心区别 | 特性 | 大端 | 小端序 | |--------------|----------------------------|----------------------------| | **存储顺** | 高位字节 → 低地址 | 低位字节 → 低地址 | | **地址增长** | 向高位地址存储低位数据 | 向高位地址存储高位数据 | | **优势场景** | 网络传输、人类直接阅读 | CPU计算(如x86/ARM架构) | | **检测方法** | 首字节≠1(见下方代码)[^4] | 首字节=1(见下方代码)[^4] | #### 三、检测系统字节的代码 ```cpp #include <iostream> int main() { int num = 1; // 十六进制 0x00000001 char *ptr = (char*)# if (*ptr == 1) { std::cout << "小端序(低位字节在低地址)" << std::endl; } else { std::cout << "大端(高位字节在低地址)" << std::endl; } return 0; } ``` **原理**:通过检查整型`num=1`的首字节(最低地址)内容: - 小端系统首字节为 `0x01`(输出1) - 大端系统首字节为 `0x00`(输出≠1)[^4] #### 四、端转换的实践代码 网络通信中需统一用**大端(网络字节)**,以下实现32位整数的端转换: ```c #include <stdio.h> unsigned int swap_endian(unsigned int num) { return ((num >> 24) & 0xFF) | // 字节3 → 字节0 ((num << 8) & 0xFF0000) | // 字节1 → 字节2 ((num >> 8) & 0xFF00) | // 字节2 → 字节1 ((num << 24) & 0xFF000000); // 字节0 → 字节3 } /* * 示例:0x12345678 * 大端→小端:0x78563412 */ ``` #### 五、关键应用场景 1. **网络协议** TCP/IP 规定使用**大端**(网络字节),`htonl()`/`ntohl()` 函数用于主机与网络转换[^3]。 2. **文件格式** UTF-16编码文件用BOM(`0xFEFF`大端,`0xFFFE`小端)声明字节[^1]。 3. **硬件设计** Verilog/FPGA开发需显式指定端(如存储器接口)[^1]。 4. **跨平台数据交换** 不同架构设备(ARM小端 vs PowerPC大端)通信时需转换端。 --- ### 相关问题 1. **如何用Python检测系统字节并实现端转换?** 2. **网络编程中为什么强制使用大端?历史原因是什么?[^3]** 3. **RISC-V架构默认采用哪种字节?能否动态切换?** 4. **端转换函数的性能优化方法(如SIMD指令应用)?[^2]** 5. **在分布式系统中,如何设计端自适应传输协议?**
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值