计算机大端模式和小端模式 内存对齐问题(sizeof)

2023-05-16

目录(?)[+]

  1. 一大端模式和小端模式的起源
  2. 二什么是大端和小端
  3. 三数组在大端小端情况下的存储
  4. 四为什么会有大小端模式之分呢
  5. 五如何判断机器的字节序
  6. 内存对齐问题
    1. 再讲讲pragma pack
  7. 内存对齐二

一、大端模式和小端模式的起源

        关于大端小端名词的由来,有一个有趣的故事,来自于Jonathan Swift的《格利佛游记》:Lilliput和Blefuscu这两个强国在过去的36个月中一直在苦战。战争的原因:大家都知道,吃鸡蛋的时候,原始的方法是打破鸡蛋较大的一端,可以那时的皇帝的祖父由于小时侯吃鸡蛋,按这种方法把手指弄破了,因此他的父亲,就下令,命令所有的子民吃鸡蛋的时候,必须先打破鸡蛋较小的一端,违令者重罚。然后老百姓对此法令极为反感,期间发生了多次叛乱,其中一个皇帝因此送命,另一个丢了王位,产生叛乱的原因就是另一个国家Blefuscu的国王大臣煽动起来的,叛乱平息后,就逃到这个帝国避难。据估计,先后几次有11000余人情愿死也不肯去打破鸡蛋较小的端吃鸡蛋。这个其实讽刺当时英国和法国之间持续的冲突。Danny Cohen一位网络协议的开创者,第一次使用这两个术语指代字节顺序,后来就被大家广泛接受。
 

二、什么是大端和小端

        Big-Endian和Little-Endian的定义如下:
1) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
2) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
举一个例子,比如数字0x12 34 56 78在内存中的表示形式为:

1)大端模式:

低地址 -----------------> 高地址
0x12  |  0x34  |  0x56  |  0x78

2)小端模式:

低地址 ------------------> 高地址
0x78  |  0x56  |  0x34  |  0x12

可见,大端模式和字符串的存储模式类似。

3)下面是两个具体例子:

16bit宽的数0x1234在Little-endian模式(以及Big-endian模式)CPU内存中的存放方式(假设从地址0x4000开始存放)为:
内存地址 小端模式存放内容 大端模式存放内容
0x4000 0x34 0x12
0x4001 0x12 0x34

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

内存地址 小端模式存放内容 大端模式存放内容
0x4000 0x78 0x12
0x4001 0x56 0x34
0x4002 0x34 0x56
0x4003 0x12 0x78

 4)大端小端没有谁优谁劣,各自优势便是对方劣势:

小端模式 :强制转换数据不需要调整字节内容,1、2、4字节的存储方式一样。
大端模式 :符号位的判定固定为第一个字节,容易判断正负


三、数组在大端小端情况下的存储:

  以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) -- 低位
        --------------

低地址


四、为什么会有大小端模式之分呢?

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


五、如何判断机器的字节序

可以编写一个小的测试程序来判断机器的字节序:

[cpp] view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. BOOL IsBigEndian()    
  2. {    
  3.     int a = 0x1234;    
  4.     char b =  *(char *)&a;  //通过将int强制类型转换成char单字节,通过判断起始存储位置。即等于 取b等于a的低地址部分    
  5.     if( b == 0x12)    
  6.     {    
  7.         return TRUE;    
  8.     }    
  9.     return FALSE;    
  10. }  
BOOL IsBigEndian()  
{  
    int a = 0x1234;  
    char b =  *(char *)&a;  //通过将int强制类型转换成char单字节,通过判断起始存储位置。即等于 取b等于a的低地址部分  
    if( b == 0x12)  
    {  
        return TRUE;  
    }  
    return FALSE;  
}

联合体union的存放顺序是所有成员都从低地址开始存放,利用该特性可以轻松地获得了CPU对内存采用Little-endian还是Big-endian模式读写

[cpp] view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. BOOL IsBigEndian()    
  2. {    
  3.     union NUM    
  4.     {    
  5.         int a;    
  6.         char b;    
  7.     }num;    
  8.     num.a = 0x1234;    
  9.     if( num.b == 0x12 )    
  10.     {    
  11.         return TRUE;    
  12.     }    
  13.     return FALSE;    
  14. }  
    BOOL IsBigEndian()  
    {  
        union NUM  
        {  
            int a;  
            char b;  
        }num;  
        num.a = 0x1234;  
        if( num.b == 0x12 )  
        {  
            return TRUE;  
        }  
        return FALSE;  
    }

内存对齐问题

怎么判断内存对齐规则,sizeof的结果怎么来的,请牢记以下3条原则:(在没有#pragma pack宏的情况下,务必看完最后一行)

1:数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储。

2:结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)

3:收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的整数倍.不足的要补齐.

[cpp] view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. typedef struct bb  
  2. {  
  3.  int id;             //[0]....[3]  
  4.  double weight;      //[8].....[15]      原则1  
  5.  float height;      //[16]..[19],总长要为8的整数倍,补齐[20]...[23]     原则3  
  6. }BB;  
  7.   
  8. typedef struct aa  
  9. {  
  10.  char name[2];     //[0],[1]  
  11.  int  id;         //[4]...[7]          原则1   
  12.  double score;     //[8]....[15]      
  13.  short grade;    //[16],[17]          
  14.  BB b;             //[24]......[47]       原则2  
  15. }AA;  
  16.   
  17. int main()  
  18. {  
  19.   AA a;  
  20.   cout<<sizeof(a)<<" "<<sizeof(BB)<<endl;  
  21.   return 0;  
  22. }   
typedef struct bb
{
 int id;             //[0]....[3]
 double weight;      //[8].....[15]      原则1
 float height;      //[16]..[19],总长要为8的整数倍,补齐[20]...[23]     原则3
}BB;

typedef struct aa
{
 char name[2];     //[0],[1]
 int  id;         //[4]...[7]          原则1 
 double score;     //[8]....[15]    
 short grade;    //[16],[17]        
 BB b;             //[24]......[47]       原则2
}AA;

int main()
{
  AA a;
  cout<<sizeof(a)<<" "<<sizeof(BB)<<endl;
  return 0;
} 

结果是

48 24
ok,上面的全看明白了,内存对齐基本过关.

再讲讲#pragma pack().

在代码前加一句#pragma pack(1),你会很高兴的发现,上面的代码输出为

32 16
bb是4+8+4=16,aa是2+4+8+2+16=32;

这不是理想中的没有内存对齐的世界吗.没错,#pragmapack(1),告诉编译器,所有的对齐都按照1的整数倍对齐,换句话说就是没有对齐规则.

明白了不?

那#pragma pack(2)的结果又是多少呢?对不起,5分钟到了,自己去测试吧.

ps:Vc,Vs等编译器默认是#pragma pack(8),所以测试我们的规则会正常;注意gcc默认是#pragma pack(4),并且gcc只支持1,2,4对齐。套用三原则里计算的对齐值是不能大于#pragma pack指定的n值。


内存对齐二

VC对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。VC 中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:

第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式;

第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。

结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为n的倍数。下面举例说明其用法:

[cpp] view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. #pragma pack(push) //保存对齐状态   
  2. #pragma pack(4)//设定为4字节对齐   
  3. struct test   
  4. {   
  5. char m1;   
  6. double m4;   
  7. int m3;   
  8. };   
  9. #pragma pack(pop)//恢复对齐状态   
#pragma pack(push) //保存对齐状态 
#pragma pack(4)//设定为4字节对齐 
struct test 
{ 
char m1; 
double m4; 
int m3; 
}; 
#pragma pack(pop)//恢复对齐状态 

以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用1个字节。接着开始为 m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了4+8+4=16个字节,满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(16),那么我们可以得到结构的大小为24。

再看下面这个例子:

[cpp] view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. #pragma pack(8)  
  2. struct S1{  
  3.  char a;  
  4.  long b;  
  5. };   
  6.   
  7. struct S2 {  
  8.  char c;  
  9.  struct S1 d;  
  10.  long long e;  
  11. };  
  12. #pragma pack()  
#pragma pack(8)
struct S1{
 char a;
 long b;
}; 

struct S2 {
 char c;
 struct S1 d;
 long long e;
};
#pragma pack()

成员对齐有一个重要的条件,即每个成员分别对齐.即每个成员按自己的方式对齐.

也就是说上面虽然指定了按8字节对齐,但并不是所有的成员都是以8字节对齐.其对齐的规则是,每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(这里是8字节)中较小的一个对齐.并且结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节.

S1中,成员a是1字节默认按1字节对齐,指定对齐参数为8,这两个值中取1,a按1字节对齐;成员b是4个字节,默认是按4字节对齐,这时就按4字节对齐,所以sizeof(S1)应该为8;

S2 中,c和S1中的a一样,按1字节对齐,而d 是个结构,它是8个字节,它按什么对齐呢?对于结构来说,它的默认对齐方式就是它的所有成员使用的对齐参数中最大的一个,S1的就是4.所以,成员d就是 按4字节对齐.成员e是8个字节,它是默认按8字节对齐,和指定的一样,所以它对到8字节的边界上,这时,已经使用了12个字节了,所以又添加了4个字节的空,从第16个字节开始放置成员e.这时,长度为24,已经可以被8(成员e按8字节对齐)整除.这样,sizeof(S2)为24个字节.

这里有三点很重要:

1.每个成员分别按自己的方式对齐,并能最小化长度。

2.复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度。

3.对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐。

 

转http://blog.csdn.NET/ce123_zhouwei/article/details/6971544

http://blog.csdn.Net/hairetz/article/details/4084088

http://www.cnblogs.com/cpoint/p/3369456.html

 未整理进来 http://blog.csdn.net/fanfank/article/details/12175585

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

计算机大端模式和小端模式 内存对齐问题(sizeof) 的相关文章

  • C++中重载与重写函数区别及虚函数(转载)

    C 43 43 中重载与重写函数区别及虚函数 C 43 43 中的虚函数 virtual function 1 简介 虚函数是C 43 43 中用于实现多态 polymorphism 的机制 核心理念就是通过基类访问派生类定义的函数 假设我
  • c#中的静态构造函数

    静态构造函数是C 的一个新特性 xff0c 其实好像很少用到 不过当我们想初始化一些静态变量的时候就需要用到它了 这个构造函数是属于类的 xff0c 而不是属于哪里实例的 xff0c 就是说这个构造函数只会被执行一次 也就是在创建第一个实例
  • C++中重写与覆写(虚函数virtual)的区别

    本文章已收录于 xff1a 虚函数的情况下调用成员函数时调用的是指向对象的所属类的成员函数例子中为apple class fruit public void func printf 34 fruit n 34 virtual void vf
  • duilib入门问题集

    引入duilib时 请确保引入头文件开始时先引入COMUTIL H头文件 include 34 COMUTIL H 34 include 34 UIlib h 34 duilib基本程序结构 在stdafx h文件中加入 cpp view
  • C#中常用的几种读取XML文件的方法

    XML文件是一种常用的文件格式 xff0c 例如WinForm里面的app config以及Web程序中的web config文件 xff0c 还有许多重要的场所都有它的身影 Xml是Internet环境中跨平台的 xff0c 依赖于内容的
  • C++(1) 指针 new 和delete

    1 概念 new typeName pointer name 61 new typeName delete delete pointer name 注意 xff1a 1 new之后要判断 xff0c 指针是否为NULL xff0c 内存被耗
  • C++的new

    C 43 43 中的new其实是一个很糊弄人的术语 xff0c 它有两种不同的含义 xff0c new运算符 xff08 new operator xff09 和new函数 xff08 operator new xff09 xff0c 值得
  • s32ds

    S32DS使用复位窗口的方式还原 xff1a 在S32DS菜单栏 Windows Perspective Reset Perspective 添加组件 xff0c 自动生成代码 xff1a
  • 在C#中使用SerialPort类实现串口通信 遇到多线程问题

    在C 中使用SerialPort类实现串口通信 遇到多线程问题 在C 中使用SerialPort类实现串口通信 2009年11月01日 星期日 10 03 在 NET work 2 0中提供了SerialPort类 xff0c 该类主要实现
  • [C#]委托和事件(详细讲解)

    引言 委托 和 事件在 Net Framework中的应用非常广泛 xff0c 然而 xff0c 较好地理解委托和事件对很多接触C 时间不长的人来说并不容易 它们就像是一道槛儿 xff0c 过了这个槛的人 xff0c 觉得真是太容易了 xf
  • 连接excel执行Insert Into语句出现“操作必须使用一个可更新的查询”的解决

    C 使用oledb连接excel执行Insert Into语句出现 操作必须使用一个可更新的查询 的解决办法 我发生错误时的环境 xff1a Windows 7 xff0c Framework 4 0 xff0c Microsoft Off
  • 操作excel的一些方法

    更改方法 xff1a public void UpdateExcelFile string filePath string prjId List lt string gt updateColNames List lt string gt c
  • C#中如何创建文件夹

    C 中对文件夹操作需要用到Directory Class 其中提供了创建 删除 移动 枚举等静态方法 该类不能被继承 以下代码实现了创建文件夹 1 2 3 4 if Directory Exists sPath
  • C#获取当前程序运行路径的方法集合

    获取当前进程的完整路径 xff0c 包含文件名 进程名 string str 61 this GetType Assembly Location result X xxx xxx xxx exe exe文件所在的目录 43 exe文件名 获
  • 怎么将excel数据导入到datagridview中

    本人小白 xff0c 想要实现EXCEL文件中的数据导入到datagridview中 xff0c EXCEL中的数据是多行多列 xff0c 行数和列数不确定 xff0c 如何实现导入到datagridview中显示 xff0c 具体的界面如
  • 总结了C#中string.format用法。分享给大家供大家参考。具体分析如下:

    String Format 方法的几种定义 xff1a String Format String Object 将指定的 String 中的格式项替换为指定的 Object 实例的值的文本等效项 String Format String O
  • c#中out、ref和params的用法与区别

    ref和out都对函数参数采用引用传递形式 不管是值类型参数还是引用类型参数 xff0c 并且定义函数和调用函数时都必须显示生命该参数为 ref out形式 两者都可以使函数传回多个结果 两者区别 xff1a 两种参数类型的设计思想不同 x
  • C#代码创建Xml文件

    扩展标记语言XML xff08 eXtensible Markup Language xff09 xff0c 是由W3C组织制定的 做为用于替代HTML语言的一种新型的标记语言 xff0c XML内部有着很多基本标准 xff0c XML就是
  • tftp使用

    tftp服务器路径 xff1a tftp使用命令 xff1a root 64 iTOP 4412 tftp g l module test ko 169 254 231 181 3356 761790 dm96 TxRound 0 for
  • 获取某一扩展名的文件集合

    获取某一扩展名的文件集合 lt summary gt lt param name 61 34 dictoryName 34 gt 目录名 lt param gt lt param name 61 34 fiterName 34 gt 扩展名

随机推荐

  • 使用XmlTextWriter生成XML文件的方法

    使用XmlTextWriter生成XML文件的方法 项目兼容需要生成一系列的xml文件 xff0c 总结了下XML文件的生成基本方式 项目兼容需要生成一系列的xml文件 xff0c 总结了下XML文件的生成基本方式 XmlTextWrite
  • datatable数据类型方法

    本文章已收录于 xff1a 43 Datatable数据类型介绍 简介方法介绍 用法一声明一个datatable类型用法二合并两个结构相同的datatable用法三datatable中数据的计算用法四两种遍历datatable的方法 Dat
  • C#字符串常见操作总结详解

    C 字符串常见操作总结详解 本篇文章是对C 中字符串的常见操作进行了详细的总结介绍 xff0c 需要的朋友参考下 xff08 1 xff09 取字符串长度 lt string gt Length xff08 2 xff09 字符串转为比特码
  • c#读取string类型的xml格式的字符串

    string str 61 lt resultInfo code 61 34 202 34 message 61 34 原密码输入不正确 xff01 34 gt XmlDocument xmlDoc 61 new XmlDocument x
  • C# 截取字符串

    本文章已收录于 xff1a str为要进行截取的字符串 start是第一个关键字 字符串 last是第二个关键字 字符串 public string GetContent string str string start string las
  • C#中文件及文件夾的遍历

    操作文件常用的类有 xff1a File 实用类 xff0c 提供许多静态方法 xff0c 用于移动 删除 和复制文件 Directory 实用类 xff0c 提供许多静态方法 xff0c 用于移动 删除和复制目录 Path 实用类 xff
  • C# 简单的XML读取修改写入

    XML概念 Root XML根节点 xff0c 只能且必须有一个 以上为LinkLibrary Element 节点元素 如Link Attribute 节点属性 如Cat Url Desc Content 内容 xff08 非空白文本 C
  • C# XML 添加,修改,删除Xml节点

    添加xml节点 private void AddXml string image string title XmlDocument xmlDoc 61 new XmlDocument xmlDoc Load Server MapPath 3
  • 使用Activator.CreateInstance完善简单工厂

    前几天在项目中看到别人的工厂类使用Activator CreateInstance 之前用简单工厂都是用switch case xff0c 之前没有用过便查了查资料 xff0c 正是这个方法 43 反射简化了工厂模式 xff0c 在需求增加
  • can协议的数据帧格式

    1 帧起始和帧结束 2 仲裁段 xff1a 3 控制段 xff1a 4 数据段 5 CRC段 6 ACK段
  • C#各种配置文件使用,操作方法总结

    配置文件操作 配置文件一般分为内置配置文和用户自定义配置文件 内置配置文件包括app config web config Settings settings等等 用户自定义配置文件一般是将配置信息放到XML文件或注册表中 xff0c 配置信
  • C#中配置文件的使用

    1 向项目添加app config文件 xff1a 右击项目名称 xff0c 选择 添加 添加新建项 xff0c 在出现的 添加新项 对话框中 xff0c 选择 添加应用程序配置文件 xff1b 如果项目以前没有配置文件 xff0c 则默认
  • C# 获取主机IP地址

    public static string GetIP string strAddr 61 34 34 try string strHostName 61 System Net Dns GetHostName System Net IPHos
  • C#中的继承与多态还有接口

    简单继承多态接口参考 简单继承 最简单的三个类 csharp view plain copy print public class Animal public Animal Debug Log 34 Construct Animal 34
  • C#操作配置文件中appSettings,connectionStrings节点

    using System using System Configuration using System Web using System Web Configuration namespace myConfiguration region
  • C# 配置文件读取与修改

    目录 43 旧方法 各位看官最好使用下面新方法新方法 配置文件在很多情况下都使用到 配置文件分为两种 一种是应用程序的配置文件 一种是web的配置文件 两种配置文件最大的区别是web的配置文件更新之后会实时更新 应用程序的配置文件不会实时更
  • AppSettings和ConnectionStrings的区别

    ConfigurationSettings AppSettings 34 34 和 ConfigurationManager ConnectionStrings 34 34 ConnectionStrings有什么区别 xff1f 晕呀 更
  • appsetting 和connectionString 的区别。

    AppSettings是ASP NET1 1时期用的 在 NET Framework 2 0中 xff0c 新增了ConnectionStrings 1 lt connectionStrings gt lt connectionString
  • 配置文件的方式总结

    1 xml文件存储配置信息 xff0c 属性中可设置输出到应用程序输出路径拷贝 xff0c 程序中读写使用 xff0c 程序修改只需修改xml文件然后覆盖到输出路径中 2 AppConfig文件存储 xff0c 可设置属性输出路径 xff0
  • 计算机大端模式和小端模式 内存对齐问题(sizeof)

    目录 43 一大端模式和小端模式的起源二什么是大端和小端三数组在大端小端情况下的存储四为什么会有大小端模式之分呢五如何判断机器的字节序内存对齐问题 再讲讲pragma pack 内存对齐二 一 大端模式和小端模式的起源 关于大端小端名词的由