一、内存分区
二、常用数据类型占用内存大小
三、给对象分配内存
1、给结构体分配内存及内存对齐
2、内存分配完后,内存里存储的到底是什么东西 --> 变量和内存的关系
一、内存分区
内存地址从低到高依次为:
- 代码区:由系统自动管理,用来存储程序编译后的二进制代码,比如函数、方法就是直接存储在代码区。
- 常量区:由系统自动管理,用来存储字符串常量。
- 静态全局区:由系统自动管理,用来存储静态变量、全局变量。
-
堆区:由程序员自己管理,我们通过
alloc
创建的对象都存储在这里,地址由低到高分配。 - 栈区:由系统自动管理,用来存储局部变量,地址由高到底分配。
@implementation ViewController
int a = 11; // 已初始化的全局变量 --> 静态全局区
int b; // 未初始化的全局变量 --> 静态全局区
static int c = 12; // 已初始化的静态全局变量 --> 静态全局区
static int d; // 未初始化的静态全局变量 --> 静态全局区
- (void)viewDidLoad {
[super viewDidLoad];
NSString *str = @"11"; // 局部变量str本身 --> 栈区,它指向的字符串常量@"11" --> 常量区
NSString *str1 = @"11"; // 局部变量str1本身 --> 栈区,它指向的字符串常量@"11" --> 常量区
static int e = 13; // 已初始化的静态局部变量 --> 静态全局区
static int f; // 未初始化的静态局部变量 --> 静态全局区
NSObject *obj = [[NSObject alloc] init]; // 局部变量obj本身 --> 栈区,它指向的对象 --> 堆区
NSObject *obj1 = [[NSObject alloc] init]; // 局部变量obj1本身 --> 栈区,它指向的对象 --> 堆区
int g = 14; // 局部变量 --> 栈区
int h; // 局部变量 --> 栈区
// 代码区 > 常量区 > 静态全局区 > 堆区 > 栈区
NSLog(@"str = %p str1 = %p", str, str1); // 常量@"11"
NSLog(@"a = %p c = %p e = %p", &a, &c, &e); // 已初始化的静态变量、全局变量
NSLog(@"b = %p d = %p f = %p", &b, &d, &f); // 未初始化的静态变量、全局变量
NSLog(@"obj = %p obj = %p", obj, obj1); // 对象
NSLog(@"g = %p h = %p", &g, &h); // 局部变量
}
@end
// 控制台打印:
str = 0x1089540b0 str1 = 0x1089540b0
a = 0x108955340 c = 0x108955348 e = 0x108955344
b = 0x108955354 d = 0x108955350 f = 0x10895534c
obj = 0x6000027601a0 obj = 0x6000027601c0
g = 0x7ffee72a917c h = 0x7ffee72a9178
二、常用数据类型占用内存大小
由于现在大多数操作系统都是64位了,所以如果我们不做特别说明,文章中都是指64位操作系统。
常用数据类型 | 占用内存空间 |
---|---|
char、unsigned char | 1个字节 |
bool | 1个字节 |
short、unsigned short int、unsigned int long、unsigned long |
2个字节 4个字节 8个字节 |
float double |
4个字节 8个字节 |
指针 | 8个字节 |
- 一个指针占用多少内存空间?
指针就是某块数据的地址,注意这里说的是指针占用多少内存空间,而不是说指针指向的那块数据占用多少内存空间。
一个指针占用多少内存空间跟开发语言无关,跟它指向的那块数据及那块数据的数据类型也无关,而仅仅是跟操作系统的寻址能力有关,操作系统是多少位的,一个指针就占用多少内存空间。比如在32位的系统上,一个指针就占4个字节,因为32位就是4个字节嘛,而现在大多数的系统都是64位的,一个指针就占8个字节。
三、给对象分配内存
1、给结构体分配内存及内存对齐
因为OC对象的本质就是结构体,所以我们需要了解一下系统是如何结构体分配内存的。
1.1 什么是内存对齐
内存对齐是指系统会按照一定的规则,为某块数据安排地址和实际占用的内存大小。解释一下,系统在给某块数据分配内存空间时,并不像我们想当然那样会把数据一段接一段地在内存上连续存储,而是有可能出现填充字节。
举个简单的例子。
struct {
char a;
int b;
} s;
NSLog(@"%ld", sizeof(s));// 8
按我们常规的理解,一个char
类型占用1个字节,一个int
类型占用4个字节,所以结构体s
会占用1 + 4 = 5个字节,结构体s
在内存上的布局就是1个字节的char
+ 4个字节的int
。
但由打印可以看出,实际上结构体s
占用了8个字节,它在内存空间上的内存布局实际上是1个字节的char
+ 3个字节的填充 + 4个字节的int
,这就是内存对齐搞的。
2.2 为什么要内存对齐
主要是为了提高CPU访问内存的效率。访问未对齐的内存,CPU需要作两次内存访问,而访问对齐的内存,仅需要作一次内存访问。
2.3 内存对齐规则
- 规则1:
针对结构体的首地址
。结构体的首地址
必须是其内部最宽(即最占内存,成员变量的绝对大小,不包含填充字节)
成员变量所占用内存
的整数倍
。
解释一下,系统在给结构体分配内存空间时,首先会找到该结构体中最宽的成员变量,得到该成员变量所占用的内存空间,然后寻找一个能是该内存空间整数倍的位置,作为该结构体的首地址。
- 规则2:
针对结构体内每个成员变量的地址和占用的内存
。第一个成员变量的地址为距离结构体首地址偏移量为0的地方,占用相应的内存空间,后面成员变量的地址为前面成员变量的地址加上前面成员变量所占用的内存,但是这个地址相对于结构体首地址的偏移量
必须是该成员变量所占用内存
的整数倍
,如有需要系统会在上一个成员变量和下一个成员变量之间加上一定的填充字节。
解释一下,系统在找到结构体的首地址后,就会开始按顺序为结构体内的成员变量依次分配内存,第一个成员变量的地址就是该结构体的首地址,第二个成员变量的地址是第一个成员变量的地址加上第一个成员变量所占用的内存,但是必须得保证第二个成员变量的地址相对于结构体首地址的偏移量是第二个成员变量占用内存的整数倍,如果不满足这个要求,系统就会在第一个成员变量和第二个成员变量之间加上一定的填充字节,以此来推后第二个成员变量的地址,从而满足这个要求。以此类推,直到给结构体内所有的成员变量都分配完内存。
- 规则3:
针对结构体的实际大小
。结构体最终的实际大小
必须是其内部最宽(成员变量的绝对大小,不包含填充字节)
成员变量所占用内存
的整数倍
,如有需要系统会在最后一个成员变量后面加上一定的填充字节。
解释一下,系统在给结构体内部所有的成员变量都分配完内存后,其实还不算完,结构体本身也要进行内存对齐,对齐规则就是上面描述的这样,这样之后才算真正完成了一个结构体的内存分配。
于是我们就知道:
一个结构体的实际大小 = 其内部成员变量占用的内存 + 因内存对齐而产生的填充内存,所以在以后的编码中,定义结构体时我们就要注意成员变量定义的先后顺序了。
同时也再次提醒:
求一个结构体的实际大小时,要包含填充字节。而寻找结构体内最宽的成员变量时,不能包含填充字节,比如当前结构体内有个成员变量刚好也是一个结构体,这个小结构体填充过字节了,它也是当前结构体的最宽成员变量,那此时我们找当前结构体的最宽成员变量时就应该是小结构体的绝对大小,不能包含填充字节。
2、内存分配完后,内存里存储的到底是什么东西 --> 变量和内存的关系
变量只是某块内存在代码层的唯一指代,用来操纵那块内存。变量本身并不存储于内存中,变量<=>内存,存储的是具体的值。
具体地说,我们每定义一个指定类型的变量,系统就会开辟一定大小的内存,并把这个变量作为这块内存在代码层的唯一指代,再把等号右边的值存进这块内存。然后我们获取变量的地址就是获取这块内存的地址,获取变量的内容就是获取这块内存的内容,也就是说我们可以在代码层通过变量来操纵这块内存了,只不过指针变量除了可以操纵它自己对应的那块内存外,还可以操纵它指向的那块内存。比如:
- 我们定义一个
int
类型的变量a
,系统就会在栈区开辟4个字节的内存,并把变量a
作为这块内存在代码层的唯一指代,再把等号右边11
这个值存进这块内存。然后我们获取变量a
的地址就是获取这块内存的地址,获取变量a
的内容就是获取这块内存的内容。
- (void)viewDidLoad {
[super viewDidLoad];
int a = 11;
NSLog(@"%p", &a); // 获取变量a的地址就是获取这块内存的地址
NSLog(@"%d", a); // 获取变量a的内容就是获取这块内存的内容
}
// 控制台打印:
0x7ffee047019c
11
- 我们定义一个指针类型的变量
obj
,系统就会在栈区开辟8个字节的内存、在堆区开辟N个字节的内存,并把变量obj
作为栈区这块内存在代码层的唯一指代,再把等号右边init
方法返回来的地址值存进这块内存。然后我们获取变量obj
的地址就是获取这块内存的地址,获取变量obj
的内容就是获取这块内存的内容。
- (void)viewDidLoad {
[super viewDidLoad];
NSObject *obj = [[NSObject alloc] init];
NSLog(@"%p", &obj); // 获取变量obj的地址就是获取这块内存的地址
NSLog(@"%p", obj); // 获取变量obj的内容就是获取这块内存的内容(这里不要蒙了啊,因为变量obj内部存储的是个地址,所以得用”%p“来获取,就像上面变量a内部存储的是个整型,得用”%d“来获取一样)
NSLog(@"%@", obj); // 还可以获取变量obj指向的那块内存的内容
}
// 控制台打印:
0x7ffee178f198
0x60000234d0d0
<NSObject: 0x60000234d0d0>
网友评论