内存对齐

一、什么是内存对齐?

    1.概念

        现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是在实际的计算机系统中,对基本类型数据在内存中存放的位置有限制,系统要求这些数据的首地址的值是某个数k的倍数,这就是所谓的内存对齐;
    
       如上,由于int型在32位的系统中占4个字节,而char型占1个字节;那么这个结构体s是占4+1 = 5个字节么?自然不是,由于内存对齐的机制存在,得到的结果是8。这个结果是怎么得到的,我们下面来解答;
    

    2.为什么要进行内存对齐?

        尽管内存是以字节为单位,但是大部分处理器并不是按字节块来存取内存,它一般会以2字节、4字节、8字节、16字节、甚至是32字节为单位来存取内存,我们将上述这些存取单位成为内存存取粒度;
        现在考虑4字节存取粒度的处理器取int类型变量(32位系统),该处理器只能从地址为4的倍数的内存开始读取数据。假如没有内存对齐机制,数据可以任意存放,现在一个int型变量存放在从地址1开始的连续4个字节的地址中,那么该处理器取数据时,要先从0地址开始读取第一个4字节块,剔除不需要的字节(0地址),然后从地址4开始读取下一个4字节块,同样剔除不要的数据(5、6、7地址),最后留下的两块数据合并放入寄存器中,这里做了许多不必要的工作。
         现在有了内存对齐,int类型数据只能存放在按照对齐规则的内存中,比如说0地址开始的内存。那么现在处理器在读取这个数据的时候,就可以一次性将数据读出来了,而不需要其他的操作,这提高了效率;
        
        
        
        

   二、内存对齐规则

    1.对齐系数

         每个平台上的编译器都有自己的默认“对齐系数”(也叫对齐模式)。gcc中默认#pragma pack(4),也就是按照4字节对齐,我们可以通过预编译命令#pragma pack(n) , n = 1、2、4、8、16来改变这一系数;
    

    2.有效对齐值

         是给定值#pragma pack(n)和结构体中最长数据类型长度中较小的那个,也就是min(n , max(struct))。有效对齐值也称之为对齐单位;
    

    3.规则

        (1)结构体中第一个成员变量的偏移量(offset)为0 , 以后每个成员变量相对于结构体首地址的offset都是该成员大小与有效对齐值中较小的那个数的整数倍,如有需要编译器会在成员之间加上填充字节;
        (2)结构体的总大小为有效对齐值的整数倍,如有需要编译器会自动在最末一个成员之后加上相应的填充字节;

    4.示例

        
        
       (1)在linux环境下,默认的#pragma pack(4),且结构体中最长的数据类型为4个字节,所以有效对齐单位为4个字节;
           分析:
                    
       (2)如果在前面加上#pragma pack(1)的话,此时的有效对齐值为min(4 , 1) = 1, 所以根据这个对齐规则的话,各个结构体的字节数为:
                    
       (3)如果加上#pragma pack(2),有效对齐值为2字节,那么根据这个对齐规则各个结构体的字节数为:
                    
            

三、#pragma pack()语法问题

    1.基本数据类型的所占内存大小

        

    2.使用#pragma pack(n)可以指定对齐系数,用#pragma pack()可以取消自定义对齐方式,恢复默认对齐。

     3.默认对齐系数:

        32位gcc---》4;
        64位gcc---》8;
        一般32位为4字节,64位为8字节;

    4.#pragma pack(push)、#pragma pack(pop)

         如果有些时候,一会想要4字节,有时候又想1字节或者8字节对齐,那么该如何解决呢?这个时候就要用到#pragma pack(push)、#pragma pack(pop)了。
         (1)#pragma pack(push)
                 编译器编译到此时的时候将保存push指令之前的对齐状态;
         (2)#pragma pack(pop)
                 编译器编译到此时的时候将恢复push指令之前保存的对齐状态
       因此,push和pop是一对应该同时出现的指令,示例如下:
        
        
        
        
        请注意:#pragma pack(pop)是恢复到push之前的对齐方式,而#pragma pack()是恢复默认对齐方式;
        

    5.#pragma pack(push , n)和#pragma pack(pop , n)

        等同于#pragma pack(push)
                 #pragma pack(n)
    
        等同于#pragma pack(pop)
                 #pragma pack(n)//这里不再恢复到push之前的对齐方式了,而是使用新的对齐方式n
            

    6.  #pragma pack(show)

        显示当前内存对齐的字节数,使用这条编译指令在编译阶段会显示一个警告,说明当前的对齐字节数
        
        
        
        

    7._attribute_(aligned(n))和_attribute_((packed))

        
        
        
    

四、其他情况

    1、静态变量static

        静态变量的存放位置与结构体实例的存储地址无关,是单独存放在静态数据区的,因此使用sizeof计算结构体大小的时候,不要将静态成员所占的空间计算进来;

    2、类

        空类是会占用内存空间的,大小为1,这是因为c++要求每个实例在内存中都有独一无二的地址;
        (1)类内部的成员变量
            【1】普通变量:是要计算内存的
            【2】static修饰的静态变量:不占用内存,编译器将其放在了全局变量区
        (2)类内部的成员函数
            【1】普通函数:不占用内存
            【2】虚函数:占4个字节,用来指定虚函数表的入口地址。所以一个类的虚函数占用的空间大小是不变的,和虚函数的个数是没有关系的;
        (3)子类
            子类所占内存大小是父类+自身成员变量的值;特别注意的是,子类与父类共享同一个虚函数指针,因此当子类新声明一个虚函数的时候,不需要对齐保存虚函数表的入口
            
            

全部评论

相关推荐

04-11 21:31
四川大学 Java
野猪不是猪🐗:(ja)va学弟这招太狠了
点赞 评论 收藏
分享
05-11 11:48
河南大学 Java
程序员牛肉:我是26届的双非。目前有两段实习经历,大三上去的美团,现在来字节了,做的是国际电商的营销业务。希望我的经历对你有用。 1.好好做你的CSDN,最好是直接转微信公众号。因为这本质上是一个很好的展示自己技术热情的证据。我当时也是烂大街项目(网盘+鱼皮的一个项目)+零实习去面试美团,但是当时我的CSDN阅读量超百万,微信公众号阅读量40万。面试的时候面试官就告诉我说觉得我对技术挺有激情的。可以看看我主页的美团面试面经。 因此花点时间好好做这个知识分享,最好是单拉出来搞一个板块。各大公司都极其看中知识落地的能力。 可以看看我的简历对于博客的描述。这个帖子里面有:https://www.nowcoder.com/discuss/745348200596324352?sourceSSR=users 2.实习经历有一些东西删除了,目前看来你的产出其实很少。有些内容其实很扯淡,最好不要保留。有一些点你可能觉得很牛逼,但是面试官眼里是减分的。 你还能负责数据库表的设计?这个公司得垃圾成啥样子,才能让一个实习生介入数据库表的设计,不要写这种东西。 一个公司的财务审批系统应该是很稳定的吧?为什么你去了才有RBAC权限设计?那这个公司之前是怎么处理权限分离的?这些东西看着都有点扯淡了。 还有就是使用Redis实现轻量级的消息队列?那为什么这一块不使用专业的MQ呢?为什么要使用redis,这些一定要清楚, 就目前看来,其实你的这个实习技术还不错。不要太焦虑。就是有一些内容有点虚了。可以考虑从PR中再投一点产出
点赞 评论 收藏
分享
评论
点赞
3
分享

创作者周榜

更多
牛客网
牛客企业服务