【OC内存管理】基础知识

目录
一、内存分区
二、常用数据类型占用内存大小
三、给对象分配内存
  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访问内存的效率,64位的操作系统是8个字节8个字节来寻址的,因此内存如果分配成8的倍数肯定查找地更快。

2.3 内存对齐规则
  • 规则1:针对结构体的首地址。结构体的首地址必须是其内部最宽(即最占内存,成员变量的绝对大小,不包含填充字节)成员变量所占用内存整数倍

解释一下,系统在给结构体分配内存空间时,首先会找到该结构体中最宽的成员变量,得到该成员变量所占用的内存空间,然后寻找一个能是该内存空间整数倍的位置,作为该结构体的首地址。

  • 规则2:针对结构体内每个成员变量的地址和占用的内存。第一个成员变量的地址为距离结构体首地址偏移量为0的地方,占用相应的内存空间,后面成员变量的地址为前面成员变量的地址加上前面成员变量所占用的内存,但是这个地址相对于结构体首地址的偏移量必须是该成员变量(如果不是结构体的话)所占用内存(如果这个成员变量刚好又是个结构体,那这里就是小结构体内最大成员变量所占用内存)整数倍,如有需要系统会在上一个成员变量和下一个成员变量之间加上一定的填充字节。

解释一下,系统在找到结构体的首地址后,就会开始按顺序为结构体内的成员变量依次分配内存,第一个成员变量的地址就是该结构体的首地址,第二个成员变量的地址是第一个成员变量的地址加上第一个成员变量所占用的内存,但是必须得保证第二个成员变量的地址相对于结构体首地址的偏移量是第二个成员变量占用内存的整数倍,如果不满足这个要求,系统就会在第一个成员变量和第二个成员变量之间加上一定的填充字节,以此来推后第二个成员变量的地址,从而满足这个要求。以此类推,直到给结构体内所有的成员变量都分配完内存。

  • 规则3:针对结构体的实际大小。结构体最终的实际大小必须是其内部最宽成员变量(如果这个成员变量刚好又是个结构体,那这里就是小结构体内实际内存即包含填充字节后所占用内存)所占用内存整数倍`,如有需要系统会在最后一个成员变量后面加上一定的填充字节。

解释一下,系统在给结构体内部所有的成员变量都分配完内存后,其实还不算完,结构体本身也要进行内存对齐,对齐规则就是上面描述的这样,这样之后才算真正完成了一个结构体的内存分配。

于是我们就知道:

一个结构体的实际大小 = 其内部成员变量占用的内存 + 因内存对齐而产生的填充内存,所以在以后的编码中,定义结构体时我们就要注意成员变量定义的先后顺序了,可以尽量把内存大小一样的成员变量挨在一起。

注意:

分析一个OC对象占用多大内存,首先就是按给结构体分配内存时的内存对齐规则分析,然后还得加上iOS系统自己的规则:那就是一个OC对象至少得占16个字节,且都得是16个字节的整数倍,这在下一篇里会有源码分析。

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>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,100评论 1 32
  • 一、温故而知新 1. 内存不够怎么办 内存简单分配策略的问题地址空间不隔离内存使用效率低程序运行的地址不确定 关于...
    SeanCST阅读 7,808评论 0 27
  • 前言 基础篇介绍了一些关于C语言内存管理的常见概念,包括内存编址、堆栈、内存操作函数、变量和数组存储简介等等。本文...
    老板娘来盘一血阅读 5,613评论 10 33
  • 前言 对于大多数开发者,特别是 C, Objective-C, Swift 等相关的开发者来说,已经很了解如何避免...
    秀才不才阅读 506评论 0 1
  • 源网址[英文] github上有大神翻译了一篇内存对齐的英文文献,我复现了一下过程; 发现其中有个地方有出入(st...
    十曰立阅读 1,196评论 0 3