目錄
- 1. 對象的內存影響因素
- 2. 成員變量在內存中的存儲情況
- 3. 結構體內存對齊
?3.1 類型占用字節數表格
?3.2 內存對齊原則
???1、數據成員對齊規則
???2、結構體作為成員
???3、結構體總大小
?3.3 舉例驗證- 4.
sizeof
、class_getInstanceSize
、malloc_size
- 5.
malloc
源碼分析
?5.1 源碼下載
?5.2 源碼分析- 6. 總結
1. 對象的內存影響因素
先創建一個SSLPerson
類:
@interface SSLPerson : NSObject {
NSString *_hobby;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
- (void)eat;
@end
打印一下它的內存大小:
把nickName
屬性注釋掉:
//@property (nonatomic, copy) NSString *nickName;
查看打印結果:
把eat
方法注視掉:
//- (void)eat;
查看打印結果:
把成員變量_hobby
注視掉:
// NSString *_hobby;
查看打印結果:
通過上面的打印,我們發現屬性
和成員變量
影響了對象內存,方法
沒有影響,所以我們可以得出最終結論:只有成員變量
會影響對象內存。
2. 成員變量在內存中的存儲情況
我們修改一下SSLPerson
類:
@interface SSLPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, copy) NSString *hobby;
@property (nonatomic, assign) int age;
@property (nonatomic) double height;
@property (nonatomic) char a;
@property (nonatomic) char b;
@end
創建SSLPerson
類的實例p
,為屬性賦值,用x/8gx
命令打印出這些值在內存中的存儲情況:
找出ssl
、王老五
、180.5
:
找出24
、a
、b
:
注:
a
的ASCII碼
為97
,b
的ASCII碼
為98
。
通過打印結果我們發現24
、a
、b
被存儲到了同一個8
字節內,這里就涉及到了內存對齊的一些原則,下面繼續探究。
3. 結構體內存對齊
3.1 類型占用字節數表格
C | OC | 32位 | 64位 |
---|---|---|---|
bool |
BOOL(64位) |
1 | 1 |
signed char |
(__signed char)int8_t、BOOL(32)位 |
1 | 1 |
unsigned char |
Boolean |
1 | 1 |
short |
int16_t |
2 | 2 |
unsigned short |
unichar |
2 | 2 |
int int32_t |
NSInteger(32位)、boolean_t(32位) |
4 | 4 |
unsigned int |
boolean_t(64位)、NSUInteger(32位) |
4 | 4 |
long |
NSInteger(64位) |
4 | 8 |
unsigned long |
NSUInteger(64位) |
4 | 8 |
long long |
int64_t |
8 | 8 |
float |
CGFloat(32位) |
4 | 4 |
double |
CGFloat(64位) |
8 | 8 |
3.2 內存對齊原則
1、數據成員對齊規則
結構體struct
或聯合體union
的數據成員,第一個數據成員放在offset
為0
的位置,以后每個數據成員存儲的起始位置要從該成員大小或者成員的子成員大小(只要該成員有子成員,比如說數組,結構體等)的整數倍數
開始(比如int
為4
個字節,則要從4
的整數倍地址開始存儲)。
2、結構體作為成員
如果一個結構里有某些結構體成員,則結構體成員要從其內部最大元素
大小的整數倍
地址開始存儲,(struct
a
中存有struct
b
,b
中有char
,int
,double
等元素,那么b
應該從8
的整數倍開始存儲),因為double
為最大子元素,占用8
個字節。
3、結構體總大小
結構體總大小,也就是sizeof
的結果,必須是其內部最大成員
的整數倍
,不足的要補齊。
3.3 舉例驗證
struct Struct1 {
double a; // 占8字節 存放在[0 7]
char b; // 占1字節 下一個索引8是1的整數倍,存放在[8]
int c; // 占4字節 下一個索引9不是4的整數倍,所以空出9,10,11,存放在 [12 13 14 15]
short d; // 占2字節 下一個索引16是2的整數倍,存放在[16 17]
}struct1; // 總區間為[0...17],大小為18,取最大元素double8字節的整倍數,所以總大小為24
struct Struct2 {
double a; // 占8字節 存放在[0 7]
int b; // 占4字節 下一個索引8是4的整數倍,存放在[8 9 10 11]
char c; // 占1字節 下一個索引12是1的整倍數,存放在[12]
short d; // 占2字節 下一個索引13不是2的整倍數,所以空出13 存放在[14 15]
}struct2; // 總區間為[0...15],大小為16,取最大元素double8字節的整倍數,所以總大小為16
struct Struct3 {
double a; // 占8字節 存放在[0 7]
int b; // 占4字節 下一個索引8是4的整數倍,存放在[8 9 10 11]
char c; // 占1字節 下一個索引12是1的整數倍,存放在[12]
short d; // 占2字節 下一個索引13不是2的整數倍,所以空出13,存放在[14 15]
int e; // 占4字節 下一個索引16是4的整數倍,存放在[16 17 18 19]
struct Struct1 str1; // 占24字節 下一個索引20不是str1中double8字節的整數倍,所以空出20 21 22 23,最后存放在[24.....47]
struct Struct2 str2; // 占16字節 下一個索引48是str2中double8字節整數倍,存放在[48.....64]
short f; // 占2字節 下一個索引65不是2的整數倍,所以空出65,存放在[66,67]
}struct3; // 總區間為[0...67],大小為68,取最大元素double 8字節的整倍數,所以總大小為72
NSLog(@"Struct1:%lu -- Struct2:%lu -- Struct3:%lu",
sizeof(struct1), sizeof(struct2), sizeof(struct3));
查看打印結果:
Struct1:24 -- Struct2:16 -- Struct3:72
4. sizeof
、class_getInstanceSize
、malloc_size
修改SSLPerson
類:
@interface SSLPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;
@end
main.m
中代碼:
#import "SSLPerson.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
SSLPerson *person = [SSLPerson alloc];
person.name = @"lee";
person.nickName = @"ssl";
NSLog(@"person %@",person);
NSLog(@"sizeof %lu",sizeof(person));
NSLog(@"person %lu",class_getInstanceSize([SSLPerson class]));
NSLog(@"person %lu",malloc_size((__bridge const void *)(person)));
}
return 0;
}
查看打印結果:
解釋:
-
sizeof
這里計算的是person
指針的大小,指針統一為8
字節; -
class_getInstanceSize
計算的是isa
指針加成員變量
占用的內存:name
(NSString``8
字節) +nickName
(NSString``8
字節) +age
(int``4
字節) +height
(long``8
字節) +isa
(來自NSObject``8
字節) =36
字節,按照8
字節對齊,最終為40
字節; -
malloc_size
計算的是實際向系統申請開辟的內存空間:40
字節向系統申請時,遵循16
字節對齊原則,最終為48
字節。
malloc
是如何申請內存的呢,我們接下來通過源碼來進行分析。
5. malloc
源碼分析
5.1 源碼下載
工程中點擊malloc_size
定位源碼所在位置:
malloc 源碼下載地址,我們以317.40.8
版本為例進行分析。
5.2 源碼分析
我們在main.m
中添加代碼,用40
字節去申請內存:
#import <Foundation/Foundation.h>
#import <malloc/malloc.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 用 40 字節去申請內存
void *p = calloc(1, 40);
NSLog(@"開辟字節數:%lu",malloc_size(p));
}
return 0;
}
查看打印結果:
確實還是開辟了48
字節的空間,下面通過斷點調試查看源碼。
斷點進入calloc
:
斷點進入:_malloc_zone_calloc
:
通過返回值找到核心代碼ptr = zone->calloc(zone, num_items, size)
,點擊進入calloc(zone, num_items, size)
發現找不到方法實現。
可以通過po
的方式找到應該調用的方法default_zone_calloc
:
- 為什么可以打印獲取:因為有賦值,就會有存儲值,就可以打印輸出。
- 第二種方式:除了輸出的方式,還可以通過
匯編找方法
的方式找到方法的真實調用。
斷點進入default_zone_calloc
:
還是找不到方法,通過p zone->calloc
獲取到方法nano_calloc
:
注:
p
比po
打印的更詳細。
斷點進入nano_calloc
:
根據返回值定位核心代碼:_nano_malloc_check_clear
,斷點進入:
根據返回值找到segregated_size_to_fit
關鍵函數,斷點進入:
這里是16
字節對齊算法,40
經過對齊后得到48
,這就是最終會開辟48
字節的原因。
6. 總結
- 堆區中,對象的內存以
16
字節對齊; - 成員變量,以
8
字節對齊。