我们大家都知道, Object-C (简称 OC) 类实际就是结构体, 如果我们想搞清楚OC对象的内存原理, 就必须先搞清楚结构体在内存中的存储情况, 比如占用的内存空间, 成员存储规则.
所以今天先来搞清楚结构体的内存情况, 大家都知道, 结构体的成员还可以是结构体, 就是结构体嵌套, 所以从是否嵌套结构体成员的角度出发, 本次我们分两大块来探讨:
- 无嵌套结构体
- 有嵌套结构体
探索环境: 64 位系统
无嵌套结构体
把结论写在最前边, 要明确一点, 一个结构体的内存空间是连续的.
无嵌套结构体内存分配规则:
- 结构体(struct)的数据成员,第一个数据成员放在 offset 为 0 的地方,以后每个数据成员存储的起始位置, 要从该成员占内存大小的整数倍下标开始存储。
- 结构体的总大小,也就是 sizeof 的结果, 必须是其内部最大成员占内存大小的整数倍. 不足的要补⻬。
接下来, 我们举例说明以上结论
首先我们先定义两个结构体, 这两个结构体成员相同, 但是成员声明的顺序不同. 然后打印他们占用内存的大小
typedef struct {
double a; // 8
char c; // 1
int b; // 4
short d; // 2
} Struct1;
typedef struct {
double a; // 8
int b; // 4
char c; // 1
short d; // 2
} Struct2;
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"Struct1 的内存: %lu", sizeof(Struct1));
NSLog(@"Struct2 的内存: %lu", sizeof(Struct2));
}
输出结果如下图:
无嵌套两个结构体内存大小
从图中我们可以看到, 两个结构体占用的内存大小是不同的, 而这两个结构体只有成员的顺序不同, 那么显然是和顺序有. 下面我们就根据以上两点规则来推演一下, 看看是不是和打印的结果相同.
两个结构体的分析图Struct1
- 成员
a
占用的内存是从Struct1
的首地址偏移(offset
) 0 开始,double
类型占8
个字节, 所以成员a
占用的空间 为 0 ~ 7. - 成员
c
占用的内存的offset
从8
开始,char
类型占1
个字节, 下标8
是1
的整数倍.所以 成员c
的占用的空间为 8. - 成员
b
占用的内存的offset
从9
开始,int
类型占4
个字节, 下标9
不是4
的整数倍. 那就继续向后查, 最小的4的倍数就是12
, 所以 成员b
的占用的空间为 12 ~ 15. - 成员
d
占用的内存的offset
从16
开始,short
类型占2
个字节, 下标16
是2
的整数倍.所以 成员d
的占用的空间为 16 ~ 17. - 按第二条规则,
Struct1
中最大的成员是a
,double
类型, 占8个字节, 因此总大小必须是 8 的整数倍, 而此时 0 ~ 17, 一共占用 18 个字节的空间. 18 并不是 8 的整数倍, 因此Struct1
分配的空间只能是更大些, 比 18 大, 同时又是 8 的倍数的最小整数就是24
.
Struct2
- 成员
a
占用的内存是从Struct1
的首地址偏移(offset
) 0 开始,double
类型占8
个字节, 所以成员a
占用的空间 为 0 ~ 7. - 成员
b
占用的内存的offset
从8
开始,int
类型占4
个字节, 下标8
正好是4
的整数倍. 所以 成员b
的占用的空间为 8 ~ 11. - 成员
c
占用的内存的offset
从12
开始,char
类型占1
个字节, 下标12
是1
的整数倍.所以 成员c
的占用的空间为 12. - 成员
d
占用的内存的offset
从13
开始,short
类型占2
个字节, 下标13
不是2
的整数倍. 那就继续向后查, 最小的2的倍数就是14
, 所以 成员d
的占用的空间为 14 ~ 15. - 按第二条规则,
Struct2
中最大的成员是a
,double
类型, 占8个字节, 因此总大小必须是 8 的整数倍, 而此时 0 ~ 16, 一共占用 16 个字节的空间. 16 正好是 8 的整数倍, 因此Struct2
分配的空间最小整数就是16
.
通过对 Struct1
和 Struct2
分析, 我们扮演的结果 与 控制台打印结果 相同, 共同验证了上面的结论. 如果你还不相信, 那可以多举例验证.
有嵌套结构体
把结论写在最前边, 非结构体类型成员同上规则, 只是多一条结构体类型成员的规则 .
无嵌套结构体内存分配规则:
- 结构体(struct)的成员,第一个成员放在 offset 为 0 的地方,以后每个成员存储的起始位置, 要从该成员占内存大小的整数倍下标开始存储。
- 如果一个结构里有结构体类型的成员, 则结构体成员要从其内部最大元素大小的整数倍地址开始存储.
- 结构体的总大小,也就是 sizeof 的结果, 必须是其内部最大成员占内存大小的整数倍. 不足的要补⻬。
我们再声明一个结构体 Struct3
, 让 Struct3
嵌套在 Struct1
和 Struct2
中:
// 总大小 16
typedef struct {
short e; // 2
double f; // 8
} Struct3;
// 总大小 40
typedef struct {
double a; // 8 0 ~ 7
char c; // 1 8
Struct3 b; // 8 16 ~ 31 (9 ~ 15 不是 8 的倍数)
short d; // 2 32 ~ 33
} Struct1;
// 总大小 32,
typedef struct {
double a; // 8 0 ~ 7
Struct3 b; // 8 8 ~ 23
char c; // 1 24
short d; // 2 26 ~ 27 (17 不是 2 的倍数)
} Struct2;
有嵌套分析图和打印结果
这次我们直接看分析图, 推理和无嵌套类似.
有嵌套这种情况要注意一下:
Struct3
的总大小是 16, 而不是 10, 按照无嵌套情况的规则计算而来.- 计算
Struct1
和Struct2
总大小时, 确定最大成员的大小时, 并不是Struct3
的大小, 而是 所以最初级类型成员的 大小作比较, 如int
,char
,double
. 所以Struct1
的大小是40
,Struct2
的大小是32
.
结论:
- 结构体(struct)的成员,第一个成员放在 offset 为 0 的地方,以后每个成员存储的起始位置, 要从该成员占内存大小的整数倍下标开始存储。
- 如果一个结构里有结构体类型的成员, 则结构体成员要从其内部最大元素大小的整数倍地址开始存储.
- 结构体的总大小,也就是 sizeof 的结果, 必须是其内部最大成员占内存大小的整数倍. 不足的要补⻬。
- 第 3 条中确定最大成员作比较时, 要包含嵌套类型的成员, 即最初级的 数据类型. 不能再拆分为止.
注意:
sizeof
是操作符, 而不是函数. 检测的是数据类型占用内存的大小.
下表是各种数据类型在 OC 中的占用内存大小, 根据对应类型来计算结构体中内存大小
网友评论