结构体内存对齐

393 阅读8分钟

对于底层的研究,我们都会关心内存的分配,今天我们先探索一下结构体内存大小的分配原则

准备工作

我们先看一下不同类型的数据所占内存大小

OC基础数据类型

NSLog(@"OC基础数据类型:\n");
NSLog(@"BOOL\t%lu\n", sizeof(BOOL));
NSLog(@"int8_t\t%lu\n", sizeof(int8_t));
NSLog(@"Boolean\t%lu\n", sizeof(Boolean));
NSLog(@"int16_t\t%lu\n", sizeof(int16_t));
NSLog(@"unichar\t%lu\n", sizeof(unichar));
NSLog(@"NSInteger\t%lu\n", sizeof(NSInteger));
NSLog(@"NSUInteger\t%lu\n", sizeof(NSUInteger));
NSLog(@"int64_t\t%lu\n", sizeof(int64_t));
NSLog(@"CGFloat\t%lu\n", sizeof(CGFloat));

打印结果

OC基础数据类型:
BOOL	1
int8_t	1
Boolean	1
int16_t	2
unichar	2
NSInteger	8
NSUInteger	8
int64_t	8
CGFloat	8

C基础数据类型:

printf("C基础数据类型:\n");
printf("bool\t%lu\n", sizeof(bool));
printf("char\t%lu\n", sizeof(char));
printf("unsigned char\t%lu\n", sizeof(unsigned char));
printf("short\t%lu\n", sizeof(short));
printf("unsigned short\t%lu\n", sizeof(unsigned short));
printf("int\t\t%lu\n", sizeof(int));
printf("unsigned int\t\t%lu\n", sizeof(unsigned int));
printf("long\t%lu\n", sizeof(long));
printf("unsigned long\t%lu\n", sizeof(unsigned long));
printf("long long\t%lu\n", sizeof(long long));
printf("float\t%lu\n", sizeof(float));
printf("double\t%lu\n", sizeof(double));

打印结果

C基础数据类型:
bool	1
char	1
unsigned char	1
short	2
unsigned short	2
int		4
int32_t		4
unsigned int		4
long	8
unsigned long	8
long long	8
float	4
double	8

C和OC基础数据类型大小对照表

16232211032627.jpg

写个🌰,先看一下大小

struct Struct1 {
    double a;       // 8    
    char b;         // 1    
    int c;          // 4  
    short d;        // 2    
}struct1;

struct Struct2 {
    double a;       // 8
    int b;          // 4
    char c;         // 1
    short d;        // 2
}struct2;

NSLog(@"struct1 size %lu \n struct2 size %lu",sizeof(struct1),sizeof(struct2));

打印结果

struct1 size 24 
struct2 size 16

对比两个结构体,有四个相同类型的基础数据,只是顺序不同,为什么得到的大小却不同呢

内存对齐原则

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

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

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

根据对齐原则,分析一波

struct1struct2里面没有结构体,根据对齐原则的第一条和第三条,分析一下struct1struct2

struct1

根据对齐原则第一条 起始位置要从该成员大小的整数倍开始

double a double是8字节 [0 7]
char b char是1字节8能被1整除 [8]
int c int是4字节 9不能被4整除 所以从12开始 (9 10 11 [12 13 14 15]
short d short是2字节,16能被2整除,[16 17]

根据对齐原则第三条 结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补⻬。

struct1 最大的是double 8字节 ,所以sizeof是24

struct2

根据对齐原则第一条 起始位置要从该成员大小的整数倍开始

double a double是8字节 [0 7]
int b int是4字节 8能被4整除[8 11]
char c char是1字节12能被1整除 [12]
short d short是2字节,13不能被2整除,所以从14开始 (13 [14 15]

根据对齐原则第三条 结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补⻬。

struct2 最大的是double 8字节 ,所以sizeof是比15大的8的整数倍,也就是16

写一个新的结构体自己分析

struct Struct3 {
    double a; 
    int b;
    char c;
    short d;
    char e;
    }struct3;

根据对齐原则第一条 起始位置要从该成员大小的整数倍开始

double a double是8字节 [0 7]
int b int是4字节 8能被4整除[8 11]
char c char是1字节12能被1整除 [12]
short d short是2字节,13不能被2整除,所以从14开始 (13 [14 15]
char e char是1字节,16能被2整除,所以从16开始 [16 17]

根据对齐原则第三条 结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补⻬。

struct3 最大的是double 8字节 ,这个正好到16,那么struct3的大小是不是16呢

打印一下

struct3 size 24

为什么是24呢,因为offset是从0开始的,所以0-16是17个字节,因此size是24。

结构体嵌套结构体

struct Struct4 {
    double a;
    int b;
    char c;
    short d;
    int e;
    struct Struct2 str;
}struct4;

double a double是8字节 [0 7]
int b int是4字节 8能被4整除[8 11]
char c char是1字节12能被1整除 [12]
short d short是2字节,13不能被2整除,所以从14开始 (13 [14 15]
int e int是4字节 16能被4整除 [16 17 18 19]

struct Struct2 str 是16字节 根据字节对齐第二条 结构体成员要从其内部最大元素大小的整数倍地址开始存储 (20 21 22 23 [24 39]

根据对齐原则第三条 结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补⻬。

struct4 最大的是struct2 16字节 ,那么struct4的大小是不是48呢

打印一下

struct4 size 40

补充第三条,收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员(非结构体成员,如果成员是结构体,看结构体里的最大成员)的整数倍,不足的要补⻬。(struct a里存有struct b,b里有char,int等元素,struct a 里有char,int ,short ,double等元素struct astruct b中最大的是double 8字节,所以sizeof是8的倍数。)

验证

struct Struct5 {
    int a;
    int b;
    char c;
    short d;
}struct5;

struct Struct6 {
    int a;
    int b;
    char c;
    short d;
    int e;
    struct Struct5 str;
}struct6;

struct Struct7 {
    double a;
    char b;
    short c;
    int d;
    struct Struct5 str;
    }struct7;

根据分析,struct5大小是12,

  1. 分析struct6

int a int是4字节 [0 3]
int b int是4字节 4能被4整除[4 7]
char c char是1字节 8能被1整除 [8]
short d short是2字节,9不能被2整除,所以从10开始 (9 [10 11]
int e int是4字节 12能被4整除,[12 15]
struct Struct5 str 12字节 但struct5最大元素是4字节 16能被4整除 [16 27]
struct5中的元素和struct6最大元素是4字节,所以sizeof是28

  1. 分析struct7

int a int是4字节 [0 3]
int b int是4字节 4能被4整除[4 7]
char c char是1字节 8能被1整除 [8]
short d short是2字节,9不能被2整除,所以从10开始 (9 [10 11]
int e int是4字节 12能被4整除,[12 15]
struct Struct5 str 12字节 但struct5最大元素是4字节 16能被4整除 [16 27]
struct5中的元素和struct7最大元素是8字节,所以sizeof是32

打印一下看结果:

struct5 size 12
struct6 size 28
struct7 size 32

总结

  1. 数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储。min(当前开始的位mn)m=9n=4 9 10 11 12
  2. 结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从struct b中最大元素double8的整数倍开始存储.)
  3. 收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员(非结构体成员,如果成员是结构体,看结构体里的最大成员)的整数倍,不足的要补⻬。(struct a里存有struct b,b里有char,int 等元素,struct a 里有char,int ,short,double等元素。struct astruct b中最大的是double 8字节,所以sizeof是8的倍数。)