【C/C++】Big Endian 和 Little Endian内存对齐

Big Endian 和 Little Endian内存对齐

由于目前的工作需要,所以学习了一下计算机内存对齐的相关知识,先介绍计算机的存储方式:Big Endian与Little Endian:

  • Big Endian 即数据的高位在低地址,地位在高地址,并且把最高字节的地址作为变量的首地址
  • Little Endian 即数据的高位在高地址,数据的低位在低地址,并且把最低字节的地址作为变量首地址。

现实中,某些基于RISC(精简指令集)的cpu比如SPARC、PowerPC等,采用Big Endian,而Intel系列cpu采用Little Endian。如果想要知道自己的电脑是什么存储格式只需要输入以下代码:

    #include<iostream>  

    using namespace std;  

    void main()  
    {  
         char ch[]={0x12,0x34,0x56,0x78};  
         int* p=(int*)ch;  
         cout<<hex<<*p<<endl;//如果是78563412,说明是 Little Endian,如果是12345678,则是Big Endian  
    } 

自然对齐:如果一个变量的内存地址正好位于它字节长度的整数倍,它就被称做自然对齐

   对于标准数据类型,它的地址只要是它的长度的整数倍,而非标准数据类型按下面的原则对齐:

  数组 :按照基本数据类型对齐,只要第一个对齐后面的自然也就对齐。
  联合 :按其包含的长度最大的数据类型对齐。
  结构体: 结构体中每个数据类型都要对齐。

字节对齐的好处:
  字节对齐的根本原因在于CPU访问数据的效率问题。学过微机原理的都知道规则字和非规则字,8086cpu访问规则字只要一个周期,而访问非规则字需要两个周期。在这里原理也是一样的,只不过这里是32位的操作系统,最多一次访问4字节,而8086是16位的,一次最多访问2字节。假设上面整型变量的地址是自然对齐,比如为0x00000000,则CPU如果取它的值的话需要访问一次内存,一次直接取从0x00000000-0x00000003的一个int型,如果变量在0x00000001,则第一次访问0x00000001的char型,第二次取从0x00000002-0x00000003的short型,第三次是0x00000004的char型,然后组合得到所要的数据,如果变量在0x00000002地址上的话则要访问两次内存,第一次为short,第二次为short,然后组合得到整型数据。如果变量在0x00000003地址上的话,则与在 0x00000001类似。

我们通过下面的例子来说明自然对齐:

    #include<iostream>  

    using namespace std;  

    void main()  
    {  
        int a=0x0abcde11;//a b c 的地址依次减小  
        int b=0x012345678;  
        double c=0x0f23456789abcdef1;  
        char d=0x0fa;  

        char *ptr=(char*)&a;  
        printf("a b每个字节的内容:\n");  
        printf("  地址  :内容\n");  
        for(int i=0;i<8;i++)  
            printf("%x  :%x\n",ptr+3-i,*(ptr+3-i));//说明整数是按 little-endian存储的  


        printf("\na b c d的首地址和地址与字节长度的余值:\n");  
        printf("a: %x :%d\n",&a,long(&a)%sizeof(a));//从这里可以看成变量的内存地址按变量顺序递减的   
        printf("b: %x :%d\n",&b,long(&b)%sizeof(b));//各个变量并不一定存放在连续的内存单元  
        printf("c: %x :%d\n",&c,long(&c)%sizeof(c));  
        printf("d: %x :%d\n",&d,long(&d)%sizeof(d));  
    }  

运行结果
这里写图片描述
由上面的结果可以知道:

  • 地址随变量顺序而减小(你可以通过改变变量定义顺序来测试);
  • 我的电脑采用的是Little Endian;
  • 各个变量并不一定存放在连续的内存单元(由c d的地址可知)

对于数组,无论是静态数组还是动态数组都是连续存储的,可以用下面程序来查看:

    #include<iostream>  

    using namespace std;  

    void main()  
    {  
        int array[5]={0};  
        for(int i=0;i<5;i++)  
        cout<<&array[i]<<endl;//输出静态数组的每个元素的地址  
        cout<<endl;  
        int *pt=new int[5];  
        for( i=0;i<5;i++)  
        cout<<hex<<(pt+i)<<endl;//输出动态数组的每个元素的地址  
        cout<<endl;  
        delete []pt;//注意要释放内存   
    }  

上面我们讨论了基本数据类型的内存存储,下面我们来看看类的存储结构:

首先我们看看下面这个类:

    class person1  
        {  
            bool m_isMan;  
            float m_height;  
            bool m_isFat;  
            double m_weight;  
            unsigned char m_books;  
        };  
        cout<<sizeof(person1)<<endl;//32=4+4+8+8+8  

这里person类的长度为32,其内存单元示意图如下:
这里写图片描述
在这里是按8字节边界来对齐的

上述变量已经都自然对齐了,为什么person对象最后还要填充7字节?

因为当你定义person类型的数组时,如果不填充7字节,则除了第一个元素外其它的元素就可能不是自然对齐了。

下面通过使用编译指令来定义对齐方式:

    #pragma pack(push,4)// 按4字节边界对齐  
        class person2  
        {  
            bool m_isMan;  
            float m_height;  
            bool m_isFat;  
            double m_weight;  
            unsigned char m_books;  
        };  
        cout<<sizeof(person2)<<endl;//24=4+4+4+8+4  
    #pragma pack(pop)     

这里person类的长度为24,其内存单元示意图如下:
这里写图片描述
显然,在这里m_weight的地址不一定能被8整除,即不一定是自然对齐的。

从上面可以知道,内存的大小和存取的效率随编译方式和变量定义有关,最好的方法是:按照字节大小从大到小依次定义变量成员,并尽可能采用小的成员对齐方式。
-从小到大定义变量:

    //按照从小到大字节长度来定义变量  
        class person4  
        {  
            bool m_isMan;  
            bool m_isFat;  
            unsigned char m_books;  
            float m_height;  
            double m_weight;  
        };  
        cout<<sizeof(person4)<<endl;//16=1+1+1+1字节的填充+4+8  

这里person类的长度为16,其内存单元示意图如下:
这里写图片描述
-从大到小定义变量:
//按照从大到小字节长度来定义变量

        class person3  
        {  
            double m_weight;  
            float m_height;  
            unsigned char m_books;  
            bool m_isMan;  
            bool m_isFat;  
        };  
        cout<<sizeof(person3)<<endl;//16=8+4+1+1+1+1字节的填充  

这里person类的长度为16,其内存单元示意图如下:
这里写图片描述
从上面可以看出两者所占内存一样,但是稳定度不同,从小到大的方式的对齐方式而发生有的成员变量不会自然对齐。如下所示

#pragma pack(push,1)// 按4字节边界对齐  
    class person5  
    {  
        bool m_isMan;  
        bool m_isFat;  
        unsigned char m_books;  
        float m_height;  
        double m_weight;  
    };  
    cout<<sizeof(person5)<<endl;//15=1+1+1+4+8  
#pragma pack(pop)  

这里person类的长度为15,其内存单元示意图如下:
这里写图片描述
在上面的程序中,double的偏移量为1+1+1+4=7,很有可能不会自然对齐,所以最好采用从大到小的方式来定义成员变量。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飛越無限

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值