内存对齐(字节对齐)两三事

什么是内存对齐

先看下面这个结构体

	struct Test{
    	int a;
    	char b;
	};

如果没有内存对齐,在64位机器上(以后不作说明,均是64位)字节内存大小应是: 4 + 1 = 5

而实际使用sizeof(struct Test)输出却得到的是8

由5到8这个过程便是内存对齐

为什么要内存对齐

本来5字节就够用了,那为什么需要占用8个字节的存储空间呢?

1. 速度

首先了解下概念:

memory access granularity(内存访问粒度): CPU把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,因此CPU在读取内存时是一块一块进行读取的,块的大小便是内存访问粒度,此值与数据总线的位数有关。
对于现代计算机来说,数据总线为64位,所以cpu读取块的大小为8字节

内存访问的首地址必定是内存访问粒度的倍数

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

这两个概念之间有什么关系呢?
对齐系数是对编译器来讲的,内存访问是对硬件来讲的

最优:对齐系数 等于 运行此代码硬件的内存访问粒度


下面实际看下cpu是怎么读取数据的

在这里插入图片描述
这里内存访问粒度为4

  • 左为内存对齐情况:数据从地址0开始,cpu从地址0开始读取,只需要读取一次,便得到[0 , 1, 2, 3]
  • 右为内存未对齐情况:数据从地址1开始,而cpu会依旧从地址0开始读取,如果想要获取想要的数据,应该怎么做呢?

    在这里插入图片描述
    可以看到,如未内存对齐,不仅需要增加内存的访问次数,还需要额外操作剔除不需要的数据。
2. 硬件原因

如果内存未对齐可能产生崩溃等问题,主要与cpu内存读取时原子性等有关

怎么内存对齐

对齐规则:

  • struct 或者 union 的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如数据、结构体等)的整数倍开始(例如int在32位机中是4字节,则要从4的整数倍地址开始存储)
  • 数据成员为结构体:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储(例如:struct a里面存有struct b,b里面有char、int、double等元素,则b应该从8的整数倍开始存储)
  • 结构体的整体对齐规则:结构体的总大小,即sizeof的结果,必须是其内部做大成员的整数倍,不足的要补齐
示例

以下分析中,如1 2 3 (4 5),内存地址1,2, 3为空穴,内存地址4,5为实际使用的内存

	struct A{
        double a; // (0 1 2 3 4 5 6 7)
        char b;   // (8)
        //每个数据成员存储的起始位置要从该成员大小的整数倍开始,这里int类型占用4字节,故应从12开始
        int c;    // 9 10 11 (12 13 14 15)  
        short d;  // (16 17)
        short e;  // (18 19)
    };

最大成员为4字节,字节对齐后是24字节

struct Test{
    char a;
};
// 最大成员为1字节, sizeof(struct Test)为1字节

struct Test{
//    short a;
};
sizeof(struct Test)也为1字节

结构体嵌套的情形呢

	struct A{
        double a; // (0 1 2 3 4 5 6 7)
        char b;   // (8)
        //每个数据成员存储的起始位置要从该成员大小的整数倍开始,这里int类型占用4字节,故应从12开始
        int c;    //  9 10 11 (12 13 14 15)  
        short d;  // (16 17)
        short e;  // (18 19)
    };
    
   struct B{
    	double a;
    	int c;
    	char b;
    	short d;
    	char e;
    	struct A s;
	};

可以转化为

	struct B1{
    	double a; // (0 1 2 3 4 5 6 7)
    	int c;    // (8 9 10 11)
    	char b;   // (12)
    	short d;  // 13 (14 15)
    	char e;  // (16)
    	//如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储
    	//在下面的结构体中最大的是double,所以成员最大字节为8,应从8字节的倍数开始
    	struct {
        	char b; //17 18 19 20 21 22 23 (24)
        	int c; //25 26 27 (28 29 30 31)
        	double a; //(32 33 34 35 36 37 38 39)
        	short d; //(40 41)
    	} s;
};

8字节对齐后,sizeof(struct B1)为48字节

另一种情况

struct A{
    double a; // (0 1 2 3 4 5 6 7)
    char b;   // (8)
    //每个数据成员存储的起始位置要从该成员大小的整数倍开始,这里int类型占用4字节,故应从12开始
    int c;    // 9 10 11 (12 13 14 15)
    short d;  // (16 17)
    short e;  // (18 19)
};

//24字节

struct B{
    double a; (0 1 2 3 4 5 6 7)
    int c;
    char b;
    short d;
    char e;
    struct A s;
    short f;
};

转化为

struct B1{
        double a; // (0 1 2 3 4 5 6 7)
        int c;    // (8 9 10 11)
        char b;   // (12)
        short d;  // 13 (14 15)
        char e;   // (16)
        //如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储
        //在下面的结构体中最大的是double,所以成员最大字节为8,应从8字节的倍数开始
        struct {
            char b;     // 17 18 19 20 21 22 23 (24)
            int c;      //25 26 27 (28 29 30 31)
            double a;   //(32 33 34 35 36 37 38 39)
            short d;    //(40 41)
            short e;    //(42 43)
        } s;
        //先将内部结构体先8字节对齐,B占用24字节,起始地址为24,所以终止地址为48
        short f;  // 48 49
};

8字节对齐后,sizeof(struct B1)为56字节

总结:如内部包含结构体成员,则一定考虑结构体成员对齐

字节对齐算法
	static inline size_t align16(size_t x) {
    	return (x + size_t(15)) & ~size_t(15);
	}
	static inline size_t align16(size_t x) {
    	return (x + size_t(15)) >> 4 << 4
	}

内存优化

	struct A{
    	char a;
    	double b;
    	char c;
	};
	
	struct B{
    	double b;
   		char a;
    	char c;
	};
	
	NSLog(@"%ld --- %ld", sizeof(struct A), sizeof(struct B));
24 --- 16

我们可以看到,虽然A和B仅成员变量的位置顺序不同,但结构体的大小却发生了变化。由此,可以通过改变成员变量的顺序,优化结构体内存大小

ios获取内存大小

  • sizeof

    C 语言提供了一个编译时(compile•time)一元运算符 sizeof,它可用来计算任一对象的长度

    表达式 sizeof(类型名/对象)

    “将返回一个整型值,它等于指定对象或类型占用的存储空间字节数。(严格地说,sizeof 的 返回值是无符号整型值,其类型为 size_t,该类型在头文件<stddef.h>中定义。)其中, 对象可以是变量、数组或结构;类型可以是基本类型,如 int、double,也可以是派生类型, 如结构类型或指针类型。” 摘录来自: (美)Brian W. Kernighan / (美)Dennis M. Ritchie. “C程序设计语言(中文第二版·新版)。”

    	struct Test t;
        NSURLSession *obj;
        //对象
        NSLog(@"%ld", sizeof(t));               // 16
        NSLog(@"%ld", sizeof(obj));             // 8
        NSLog(@"%ld", sizeof(1));               // 4
        //类型
        NSLog(@"%ld", sizeof(struct Test));     // 16
        NSLog(@"%ld", sizeof(NSURLSession *));  // 8
        NSLog(@"%ld", sizeof(int));             // 4
    

    若传入对象类型,输出结果为8,为指针类型占用内存大小。因此不能通过该运算符计算对象占用内存大小

  • class_getInstanceSize

    对象实际所需内存大小(8字节对齐)

  • malloc_size

    实际分配内存大小(16字节对齐)

    class_getInstanceSize <= malloc_size

附录各类型大小

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值