美文网首页
内存对齐

内存对齐

作者: ROBIN2015 | 来源:发表于2018-08-22 17:44 被阅读34次

    内存对齐

    概念:

    百度百科内存对齐:编译器为程序中的每个“数据单元”安排在适当的位置上

    为什么要对齐?

    一种提高内存访问速度的策略,cpu在访问未对齐的内存需要经过两次内存访问,而经过内存对齐一次就可以了.

    举例说明: 32bit的系统一次只能读入32bit的数据, 即4个字节, 如果首地址为0, name读入顺序应该是0-3, 3-7, 8-11 ...... 这样的读入; 在没有对齐的情况下, 一个int变量(4字节)可能分配中2,3,4,5这几个位置上, 如果要获取该变量, 就要进行0-3, 4-7两次读取

    像下面的struct:

    typedef strutc test{
        char a;
        int b;
        char c;
    } 
    
    • 未对齐
    image

    读取b需要0-3, 4-7两次读取

    • 对齐
    image

    读取b就可以从4-7一次读取

    对齐规则

    1. 内存的自然对齐

    每一种数据类型都必须放在地址中的整数倍上

    • char(1)类型可以放在任何位置
    • short(2)只能放在0x2, 1x2, 2x2, 3x2.., 即0, 2, 4, 6 ...的位置上
    • int(4)只能放在0x4, 1x4, 2x4, 3x4.., 即0, 4, 8, 12 ...的位置上
    • double(8)只能放在0x8, 1x8, 2x8, 3x8.., 即0, 8, 16, 24 ...的位置上

    note:以上规则针对的是#pragma pack(16)#pragma pack(8)时适用, 对其系数为1,2,4, 参见下边的说明

    代码:

    #import "ViewController.h"
    
    typedef struct test{
        char a;
        int b;
        double c;
        char d;
    }STU;
    @interface ViewController ()
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        test();
    }
    
    int test(void)
    {
        STU s;
        printf("s的大小是 = %d\n",(int)sizeof(STU));
        printf("s中a的起始地址是 %p\n",&(s.a));
        printf("s中b的起始地址是 %p\n",&(s.b));
        printf("s中c的起始地址是 %p\n",&(s.c));
        printf("s中d的起始地址是 %p\n",&(s.d));
        return 0;
    }
    @end
    

    输出:

    s的大小是 = 24
    s中a的起始地址是 0x7ffee06479c8
    s中b的起始地址是 0x7ffee06479cc
    s中c的起始地址是 0x7ffee06479d0
    s中d的起始地址是 0x7ffee06479d8
    

    如果按照规则一, 推断出来的应该是这样的:

    image

    四字节对齐, 长度应该是20位

    结果其实是这样的:

    image

    长度24位, 最后一个char补了7位, 原因就在于规则二

    另外修改对齐系数, 的出来的结果也不一样:

    pragma pack(16)
    s的大小是 = 24
    s中a的起始地址是 0x7ffee3e7f9c8
    s中b的起始地址是 0x7ffee3e7f9cc
    s中c的起始地址是 0x7ffee3e7f9d0
    s中d的起始地址是 0x7ffee3e7f9d8
    
    pragma pack(8)
    大小是 = 24
    s中a的起始地址是 0x7ffee2ab49c8
    s中b的起始地址是 0x7ffee2ab49cc
    s中c的起始地址是 0x7ffee2ab49d0
    s中d的起始地址是 0x7ffee2ab49d8
    
    pragma pack(4)
    s的大小是 = 20
    s中a的起始地址是 0x7ffee81239c8
    s中b的起始地址是 0x7ffee81239cc
    s中c的起始地址是 0x7ffee81239d0
    s中d的起始地址是 0x7ffee81239d8
    
    pragma pack(2)
    s的大小是 = 16
    s中a的起始地址是 0x7ffee03f09d0
    s中b的起始地址是 0x7ffee03f09d2
    s中c的起始地址是 0x7ffee03f09d6
    s中d的起始地址是 0x7ffee03f09de
    
    
    pragma pack(1)
    s的大小是 = 14
    s中a的起始地址是 0x7ffee2eb49d0
    s中b的起始地址是 0x7ffee2eb49d1
    s中c的起始地址是 0x7ffee2eb49d5
    s中d的起始地址是 0x7ffee2eb49dd
    

    2.补齐规则

    在经过第一原则分析后,检查计算出的存储单元是否为所有元素中最宽的元素的长度的整数倍,是,则结束;
    若不是,则补齐为它的整数倍
    

    因为代码中:

    typedef struct test{
        char a;
        int b;
        double c;
        char d;
    }STU;
    

    struct的成员类型double是8位长度, 所以struct的大小必须是8的倍数, 所以最后一位char要补上7位

    3. 包含结构体成员的补齐规则

    如果结构体作为成员,则要找到这个结构体中的最大元素,然后从这个最大成员的整数倍地址开始存储
    

    代码:

    typedef struct
    {
        char a;
        int b;
        double c;
    }X;
    
    typedef struct {
        char a;
        X b;
    }Y;
    

    sizeof(X)为16,sizeof(Y)为24, 计算Y的存储长度时,X的最大元素是double, 所以在存放第二个元素b时的初始位置是在double型的长度8的整数倍处,而非16的整数倍处,即系统为b所分配的存储空间是第8~23个字节

    另外:包含指针类型的情况。只要记住指针本身所占的存储空间是8个字节(64bit系统, 32bit下是4字节)就行了,而不必看它是指向什么类型的指针

    typedef struct
    {
        int* b;
    }X;
    
    typedef struct {
        char *a;
    }Y;
    

    输出:

    X的大小是 = 8
    Y的大小是 = 8
    

    对齐规则(百科中的描述)

    每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。

    1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。

    2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

    3、结合1、2可推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。

    32bit与64bit字节对比:

    image

    参考:

    结构体在内存中的对其规则

    内存对齐与内存分配原则

    相关文章

      网友评论

          本文标题:内存对齐

          本文链接:https://www.haomeiwen.com/subject/ijiyiftx.html