內(nèi)存管理的問(wèn)題#
??先看看下面的幾段代碼,重溫一下使用內(nèi)存常見的問(wèn)題。
#include <stdio.h>
#include <stdlib.h>
int a = 2;
void foo() {}
int main() {
char str1[20] = "Gello, world!";
char *str2 = "Gello, world!";// 只讀數(shù)據(jù)段,無(wú)法更改,內(nèi)存使用錯(cuò)誤
char *str3 = (char *)malloc(1000);// 申請(qǐng)空間
//str[0] = 'H';//只讀數(shù)據(jù)段無(wú)法更改 bos error
str1[0] = 'H';
printf("棧(stack):str1 = %p\n", str1);
printf("堆(head):str3 = %p\n", str3);
printf("數(shù)據(jù)段:a = %p\n", &a);
printf("只讀數(shù)據(jù)段:str2 = %p\n", str2);
printf("代碼段:str2 = %p\n", foo);
free(str3);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void get_memory(char *p, int capacity) {
p = malloc(sizeof *p * capacity);
}
// 任何時(shí)候希望通過(guò)函數(shù)調(diào)用修改傳入的參數(shù)
// 那就不能只傳參數(shù)的值 而要傳參數(shù)的地址
// 如果傳入的參數(shù)本身就是地址 那么就要使用指向指針的指針
// 指針的第一個(gè)用途就是實(shí)現(xiàn)跨棧的操作
void get_memory2(char **p, int capacity) {
// 指針的第二個(gè)用途就是申請(qǐng)堆空間
*p = malloc(sizeof **p * capacity);
}
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int a = 5, b = 10;
printf("a = %d, b = %d\n", a, b);
swap(&a, &b);// 交換 a 和 b 的值
printf("a = %d, b = %d\n", a, b);
char *str = NULL;
// 未申請(qǐng)到堆空間,只是為形參申請(qǐng)了100字節(jié)的空間
// 為指針申請(qǐng)空間,需要傳指針的地址,用二重指針(指針的指針)
//get_memory(str, 100);
get_memory2(&str, 100);
if (str)// 判斷是否申請(qǐng)到空間
{
strcpy(str, "Hello, world!");
printf("%s\n", str);
free(str);// 堆空間不會(huì)隨著棧的消失而消失,需要手動(dòng)釋放
str = NULL;
}
return 0;
}
#include <stdio.h>
char *get_memory() {
// c中數(shù)組的數(shù)據(jù)放在??臻g中
// str是一個(gè)局部變量,調(diào)用結(jié)束后會(huì)自動(dòng)釋放
// 一個(gè)函數(shù)可以返回??臻g的數(shù)據(jù)但不能返回??臻g的地址
char str[] = "hello, world";
char *str2 = "hello, world";// 放在只讀數(shù)據(jù)段中
// return str;// 無(wú)法返回??臻g的地址
return str2;
}
void main() {
char *str = get_memory();
if (str != NULL) {
printf("%s\n", str);
// 沒(méi)有申請(qǐng)空間(malloc), 不能釋放堆空間
// free操作跟malloc操作是成對(duì)出現(xiàn)的
//free(str);
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *get_memory(int capacity) {
char *str = malloc(sizeof *str * capacity);
return str;
}
void foo(char *str) {
// ...
free(str);
str = NULL;
}
void bar(char *str) {
strcpy(str, "Hello, world!");
printf("%s\n", str);
// 用完內(nèi)存一定需要釋放, 否則有內(nèi)存泄露的風(fēng)險(xiǎn)
free(str);
str = NULL; // important
}
int main() {
char *str = get_memory(100);
// 申請(qǐng)內(nèi)存以后一定要先判斷再使用
if (str) {
bar(str);// 調(diào)用完后若有其他函數(shù)調(diào)用str,提前釋放了str,會(huì)出錯(cuò)
//foo(str);// 重復(fù)釋放
}
return 0;
}
??C語(yǔ)言中內(nèi)存操作常見錯(cuò)誤:
內(nèi)存分配未成功就開始使用內(nèi)存。
內(nèi)存分配雖然成功但尚未初始化就使用。
內(nèi)存分配成功且已經(jīng)初始化但訪問(wèn)越界。
使用realloc()函數(shù)不使用備用指針。
內(nèi)存泄露(申請(qǐng)了堆空間,但在使用結(jié)束后忘記釋放)。
提前釋放(釋放了內(nèi)存卻仍然在使用的空間,導(dǎo)致數(shù)據(jù)不安全)。
重復(fù)釋放(釋放一個(gè)已經(jīng)釋放過(guò)的空間,導(dǎo)致程序崩潰)。
??上述問(wèn)題中的第5項(xiàng)和第6項(xiàng)在實(shí)際開發(fā)中,尤其是遇到模塊化的團(tuán)隊(duì)開發(fā)或者程序中使用多線程的時(shí)候,顯得尤為難以處理。為了解決上述問(wèn)題,在Objective-C中引入了引用計(jì)數(shù)的概念。Objective-C中每個(gè)類都是NSObject子類,因此每個(gè)對(duì)象都有一個(gè)內(nèi)置的計(jì)數(shù)器,這個(gè)計(jì)數(shù)器稱為引用計(jì)數(shù)(Reference Count),也稱保留計(jì)數(shù)(Retain Count)。所謂Objective-C的內(nèi)存管理,就是要維護(hù)引用計(jì)數(shù)器正確+1和-1,當(dāng)引用計(jì)數(shù)器為0時(shí),對(duì)象正確釋放。在Objective-C中,每個(gè)對(duì)象就如同一個(gè)QQ討論組,當(dāng)有人創(chuàng)建討論組時(shí),討論組人數(shù)為1(對(duì)象創(chuàng)建);每有一個(gè)人加入討論組,該討論組的人數(shù)+1(使用retain增加引用計(jì)數(shù)),每有一個(gè)人離開討論組,該討論組的人數(shù)-1(使用release減少引用計(jì)數(shù));如果討論組的人數(shù)為0,則自動(dòng)解散。
??和內(nèi)存管理相關(guān)的方法:
retain:增加對(duì)象的引用計(jì)數(shù)。
release:減少對(duì)象的引用計(jì)數(shù)。
autorelease:在自動(dòng)釋放池塊結(jié)束時(shí)減少對(duì)象的引用計(jì)數(shù)。
retainCount:引用計(jì)數(shù)的數(shù)量。
將整個(gè)項(xiàng)目都改成手動(dòng)內(nèi)存管理
操作:(選擇no)

- 將項(xiàng)目中的某個(gè)文件改成手動(dòng)內(nèi)存管理
操作:選擇需要改為手動(dòng)管理的m文件添加 -fno-objc-arc
- 將項(xiàng)目中的某個(gè)文件改成手動(dòng)內(nèi)存管理

如何有效管理內(nèi)存##
??C語(yǔ)言中內(nèi)存操作要牢記以下幾點(diǎn):
用malloc()/realloc()/calloc()申請(qǐng)內(nèi)存后,應(yīng)理解檢查是否為NULL。
不要忘記為數(shù)組或動(dòng)態(tài)申請(qǐng)的內(nèi)存賦值,防止未初始化的內(nèi)存作為運(yùn)算的右值。
內(nèi)存操作要小心邊界,防止操作越界。
分配內(nèi)存和釋放內(nèi)存的操作必須配對(duì),防止內(nèi)存泄露。
用free()函數(shù)釋放內(nèi)存后將指針賦值為NULL,防止產(chǎn)生野指針。
??Objective-C中使用內(nèi)存的原則基本上是:
分配內(nèi)存的操作要和釋放內(nèi)存的操作成對(duì)出現(xiàn)。
誰(shuí)分配了內(nèi)存,誰(shuí)就要負(fù)責(zé)回收此內(nèi)存。
??特殊情況:
成員變量是對(duì)象指針,應(yīng)在析構(gòu)方法(dealloc)中釋放。
如果發(fā)生指針的轉(zhuǎn)移,應(yīng)釋放舊對(duì)象,retain新對(duì)象。
從對(duì)象持有者(如NSArray、NSDictionary等)中取出對(duì)象的指針,如需長(zhǎng)期使用,需要retain。
??ARC自動(dòng)釋放池的代碼示例
#import <Foundation/Foundation.h>
#import "YHStudent.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 野指針(釋放了仍然還在使用)和內(nèi)存泄露(未正確的釋放)
// 1、創(chuàng)建了一個(gè)學(xué)生對(duì)象(堆上)
// 2、創(chuàng)建了一個(gè)指針(棧上)
// 3、指針指向了學(xué)生能夠?qū)ο?指針中存儲(chǔ)了學(xué)生對(duì)象的地址)
__weak YHStudent *stu = [[YHStudent alloc] init];// 弱指針,一初始化就銷毀 ,stu直接被銷毀
__strong YHStudent *stu2 = [[YHStudent alloc] init];// 強(qiáng)指針
YHStudent *student = [[YHStudent alloc] init];// 強(qiáng)指針
stu2 = nil;//若賦值為空,則在這被釋放,stu2在這里被銷毀
}// 在這兒被釋放(由于出了強(qiáng)指針(student)的作用域)
return 0;
}
#import <Foundation/Foundation.h>
@interface YHStudent : NSObject
@property (nonatomic, copy) NSString *name;
@end
#import "YHStudent.h"
@implementation YHStudent
//ARC中可以重寫dealloc方法,但是覺(jué)對(duì)不見而已調(diào)用父類的dealloc方法
//在對(duì)象將要銷毀的時(shí)候會(huì)自動(dòng)調(diào)用dealloc
- (void) dealloc {
NSLog(@"學(xué)生被銷毀!!");
}
@end
-
四個(gè)關(guān)鍵字的使用
__strong(強(qiáng)引用):缺省屬性,其修飾的對(duì)象指針,指向哪個(gè)對(duì)象,會(huì)對(duì)該對(duì)象retain,離開哪個(gè)對(duì)象,會(huì)對(duì)該對(duì)象release。
__weak(弱引用):其修飾的對(duì)象指針,指向任何對(duì)象都不會(huì)retain。這樣的指針指向的對(duì)象隨時(shí)可能消失。如果對(duì)象消失了,這個(gè)指針會(huì)自動(dòng)變成nil。
__unsafe_unretained:其修飾的對(duì)象指針,指向任何對(duì)象都不retain。當(dāng)指向的對(duì)象消失,該指針不會(huì)變成nil,仍然指向已經(jīng)釋放的對(duì)象。
__autoreleasing:只用來(lái)修飾需要被傳入地址的指針。
-
屬性修飾符
- copy:控制@property實(shí)現(xiàn)的set方法,會(huì)先創(chuàng)建一個(gè)新的對(duì)象,將參數(shù)的值傳給新的對(duì)象,最后將新的對(duì)象賦值給成員變量.常用來(lái)修飾字符串、block、數(shù)組、字典、NSData;
- strong:控制@property實(shí)現(xiàn)符合內(nèi)存管理的set方法,引用計(jì)數(shù)加1;修飾一般的對(duì)象(retain的替代品)
- weak:控制@property實(shí)現(xiàn)一般的set方法(直接賦值),修飾對(duì)象用來(lái)避免循環(huán)引用(最常用的是delegate)
- assign:控制@property實(shí)現(xiàn)一般的set方法(直接賦值);常用來(lái)修飾基本數(shù)據(jù)類型(int、float、char、結(jié)構(gòu)體、枚舉、聯(lián)合體)
- retain:在MRC中相當(dāng)于strong(實(shí)現(xiàn)的set方法就是舊值release、新值retain)。
牢記在ARC有效時(shí)retain/release/autorelease/retainCount都不能用。
不能顯式調(diào)用dealloc析構(gòu)器,析構(gòu)器中可以將成員變量中的指針賦值為nil。
用@autoreleasepool{}替代NSAutoreleasePool對(duì)象的創(chuàng)建。
在ARC有效時(shí)id和void *不再等同,需要用__bridge轉(zhuǎn)換。
不要在C的結(jié)構(gòu)體中聲明對(duì)象指針,否則無(wú)法進(jìn)行內(nèi)存管理。
管理內(nèi)存的一些常見問(wèn)題##
-
內(nèi)存管理的作用:
- 解決內(nèi)存泄露和野指針操作
-
為什么要內(nèi)存管理,我們要注意的問(wèn)題是什么?
- 解決內(nèi)存泄露和野指針操作
-
什么是黃金法則?
- 內(nèi)存管理原則:誰(shuí)創(chuàng)建誰(shuí)釋放,在哪兒創(chuàng)建在哪兒釋放
@property參數(shù)(retain)要注意的問(wèn)題:避免循環(huán)引用
-
什么時(shí)候autorelease?與release的區(qū)別
- 對(duì)象需要延時(shí)銷毀的時(shí)候使用autorelease
- autorelease是將對(duì)象添加到自動(dòng)釋放池中(延時(shí)對(duì)象的銷毀),release將對(duì)象的引用計(jì)數(shù)器減1
-
什么是自動(dòng)釋放池
- 注意:autorelease和autoreleasepool是成對(duì)出現(xiàn)的
- autoreleasepool的原理:當(dāng)autoreleasepool銷毀的時(shí)候,會(huì)將自動(dòng)釋放池中所有的對(duì)象調(diào)用一次release方法
- autorelease的作用:將對(duì)象放入自動(dòng)釋放池中(并不是寫在自動(dòng)釋放池的大括號(hào)中的對(duì)象就是在自動(dòng)釋放池中的對(duì)象)
-
一個(gè)工程中能有一個(gè)自動(dòng)釋放池?
- 錯(cuò),可以NSAutoreleasePool或者@Autoreleasepool{}去創(chuàng)建多個(gè)自動(dòng)釋放池
-
在手動(dòng)內(nèi)存管理中,盡量都使用autorelease?
- 錯(cuò),對(duì)象調(diào)用autorelease會(huì)延遲對(duì)象的銷毀,如果所有的對(duì)象都延遲銷毀的話,相當(dāng)于沒(méi)有做內(nèi)存管理
-
對(duì)內(nèi)存管理的理解?(原理)重點(diǎn)
手動(dòng)(MRC):1、在創(chuàng)建一個(gè)對(duì)象的時(shí)候系統(tǒng)會(huì)自動(dòng)創(chuàng)建這個(gè)對(duì)象的引用計(jì)數(shù),并且賦值為1;2、當(dāng)引用計(jì)數(shù)為0的時(shí)候,對(duì)象會(huì)去調(diào)用dealloc方法,來(lái)銷毀對(duì)象;3、對(duì)象調(diào)用release方法會(huì)讓引用計(jì)數(shù)減1,調(diào)用retain方法讓對(duì)象的引用計(jì)數(shù)加1。
自動(dòng)(ARC):在ARC中管理內(nèi)存的實(shí)質(zhì)還是通過(guò)引用器去管理的,但是程序員不再去關(guān)心引用計(jì)數(shù)的值。在ARC環(huán)境下,系統(tǒng)會(huì)在程序編譯的時(shí)候會(huì)自動(dòng)在合適的地方添加retain、release或者autorelease。
當(dāng)有強(qiáng)指針指向?qū)ο蟮臅r(shí)候,對(duì)象不銷毀;弱指針不影響對(duì)象的銷毀;指針默認(rèn)都是強(qiáng)指針
__weak 使用這個(gè)關(guān)鍵字修飾的指針是弱指針;__strong 使用這個(gè)關(guān)鍵字修飾的指針是強(qiáng)指針(默認(rèn)值);
-
手動(dòng)內(nèi)存管理的原則?
- 程序中如果出現(xiàn)alloc、retain、new必須配對(duì)出現(xiàn)一個(gè)release或者autorelease(誰(shuí)創(chuàng)建誰(shuí)釋放,在哪兒創(chuàng)建在哪兒釋放)
-
3、 autoreleasepool的原理和autorelease的作用
- autoreleasepool的原理:當(dāng)autoreleasepool銷毀的時(shí)候,會(huì)將自動(dòng)釋放池中所有的對(duì)象調(diào)用一次release方法
- autorelease的作用:將對(duì)象放入自動(dòng)釋放池中(并不是寫在自動(dòng)釋放池的大括號(hào)中的對(duì)象就是在自動(dòng)釋放池中的對(duì)象)
4、 MRC中符合內(nèi)存管理setter函數(shù) 的書寫(舊值release,新值retain,然后賦值)