在iOS或者其他系统中,程序运行中的存储根据功能分类,基本都有这几个分类:
- 栈区:用于存放临时内容,一般为局部变量,多为变量指针。
- 堆区:存放可能需要保存时间长些的内容,这里存储需要申请存储空间,在OC中一般存放OC对象、malloc申请的结构体等。
- 数据区:存放常量(包括静态常量, 常量字符串)等。
这里说的静态常量与是否有static、const修饰无关。
本文代码运行于Xcode模拟器iphoneX,64位内核,16位地址,以下出现指针地址不足16位的高位都补0;其栈地址一般是0x7ffeed386a60 这样以7为起始的12位16进制地址;堆地址一般是0x600000220df8 这样以6为起始的12位16进制地址;数据区地址一般是0x10287feaf 这样以1为起始的9位16进制地址。
先看通常情况下的变量存储,用代码演示,本文所提到的输出地址都是采用代码printf(“%p”,value);
:
1.存储在栈区
+ (void)theory
{
int i = 88;
char a = 'a';
}
上面这个变量i的地址 &i 为0x7ffeed386a6****0 这里是局部变量,i是直接存储在栈中的,&i就是这个栈内存的地址。
+ (void)theory
{
NSString *str0 = @"a";
}
输出&str0:0x7ffeea2efcd8。str0是一个指针,指向字符传常量@“a”的指针,&str0是该指针的地址,该指针依然是局部变量,虽然字符串常量在堆中,但是该指针存储在栈中。
2.存储在堆区
typedef struct SY__block_impl_test0 {
struct __block_impl impl;
char *ss;
int saf;
} SY__block_impl;
SY__block_impl *aas = malloc(sizeof(SY__block_impl));
LearnStruct *stru = [[LearnStruct alloc] init];
结构体使用malloc是在堆上申请内存空间的,aas的输出地址:0x604000457a30 这是一个堆地址。
在OC中使用alloc方法创建的对象都是在堆中,oc对象也是结构体,所以也需要在堆区申请内存空间,上述stru输出地址:0x604000201af0 这同样是一个堆地址
3.存储在数据区
该区会存放全局数据:
@interface StoreTheory()
...
@end
NSString *stringGloba;
NSString *stringGloba1 = @"123321123a";
NSObject *obj = nil;
@implementation StoreTheory
...
@end
这里的三个全局变量的指针都是存储在数据区的:
&stringGloba -> 0x10965f608
&stringGloba1 -> 0x10965f458
&obj -> 0x10965f600
数据区也分初始化数据(相对未初始化数据地址低)和未初始化数据,从&stringGloba和&obj的地址也可以看出来两个未初始化的数据是相连的。
存放字符串常量:
NSString *stringGloba1 = @"123321123a";
NSString *str0 = @"a";
NSString *str1 = @"b";
NSString *string0 = @"a";
NSString *string1 = @"b";
这里的stringGlobal的输出地址:0x10a722790 ,字符串存储在数据区;这里还能发现str0和string0的地址都是:0x10a7227b0,str1和string1的地址也一样:如果是一样的字符串,那么他们都是指向同一个地址,系统不会重新申请空间存放一样的字符串常量。
以上讲了一些变量、常量的存储位置,接下来补充讲解下block的存储细节。
Blk blockTest = ^() {
printf("assa\n");
};
这是一个全局block,blockTest的输出是0x10a4714f0 表明了全局block的身份,&blockTest的输出是0x7ffee57967f8 这里blockTest是局部变量,地址在栈中也正常,但是我们会发现如果我们在控制台用po命令打印blockTest:
po blockTest
0x000000010a46c2e0
为什么会出现一个比printf("%p",blockTest)
地址要低的这个地址呢?我们可以想象比数据区的地址还低的是不是就是代码区,我们再回顾下block的clang源码:
struct __ClangProxy__staff_block_impl_0 {
struct __block_impl impl;
struct __ClangProxy__staff_block_desc_0* Desc;
__ClangProxy__staff_block_impl_0(void *fp, struct __ClangProxy__staff_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __ClangProxy__staff_block_func_0(struct __ClangProxy__staff_block_impl_0 *__cself) {
printf("assa\n");
}
static struct __ClangProxy__staff_block_desc_0 {
size_t reserved;
size_t Block_size;
} __ClangProxy__staff_block_desc_0_DATA = { 0, sizeof(struct __ClangProxy__staff_block_impl_0)};
我们可以推测,我使用printf("%p",blockTest) 打印的地址其实是结构体struct __ClangProxy__staff_block_impl_0
存储在数据的地址,而直接在控制台po打印的地址是block的实现方法void __ClangProxy__staff_block_func_0(struct __ClangProxy__staff_block_impl_0 *__cself)
在代码区的首地址,为了验证这一猜测,我添加了这个方法:
void userWithBlock()
{
printf("%p",userWithBlock);
}
这个打印结果是0x10a46b800 这和block的直接打印地址在同一区段。
并且,我在打印一个堆block时发现堆block的po打印地址是(void (^)()) myBlock = 0x000000010a46c310
这个地址更接近0x10a46c2e0 ,由此也能看出来确实是代码区地址,各类型block也只有block实现方法是统一存储在代码区的。
__block int h = 9;
__block char *string = str;
NSMutableString * string0 = [[NSMutableString alloc]initWithString:@"123"];
__block int *te = tt;
printf("h--地址%p\n",&h);
printf("string--地址%p\n",string);
printf("&string--地址%p\n",&string);
printf("te--地址%p\n",te);
printf("&te--地址%p\n",&te);
void(^staBlock1)(void) = ^{
printf("h--地址%p\n",&h);
printf("string--地址%p\n",string);
printf("&string--地址%p\n",&string);
printf("te--地址%p\n",te);
printf("&te--地址%p\n",&te);
h = 13;
printf("h--地址%p",&h);
te=&h;
printf("te--地址%p",te);
printf("&te--地址%p",&te);
string = "bbssaaaaaaaasdf";
printf("string--地址%p\n",string);
printf("&string--地址%p\n",&string);
[string0 appendString:@"456"];
};
上述代码打印结果:
h--地址0x7ffee6f3f6f0
string--地址0x108cc6e2d
&string--地址0x7ffee6f3f618
te--地址0x7ffee6f3f654
&te--地址0x7ffee6f3f5c8
h--地址0x604000431cd8
string--地址0x108cc6e2d
&string--地址0x60000023d658
te--地址0x7ffee6f3f654
&te--地址0x604000431cf8
h--地址0x604000431cd8
te--地址0x604000431cd8
&te--地址0x604000431cf8
string--地址0x108cc6f0b
&string--地址0x60000023d658
首先要知道这里的block是从栈copy到堆的NSMallocBlock,然后看各个变量,
-
h:从这里可以看出h在使用__block修饰后,在block被copy到堆后,变量block也被copy到堆上了,如果熟悉block的clang源码就知道这里因为h转换成了结构体,结构体重新在堆上申请了内存空间,结构体的成员h是值拷贝到堆上的;所以h在block从block外的栈地址转变成了block内的堆地址,其赋值是改变内存中存储的值,不会使指针变化。
-
string:string是字符串常量的指针,在字符串没有改变时,它始终指向数据区的该字符串常量地址,所以不会改变,但是当字符串发生改变的时候,地址自然会出现改变。
-
&string:这是字符串指针string的存储地址,使用__block转换为结构体也是存储在栈的,在block外的时候是栈地址;在block内则会因为堆block而拷贝到堆上;这里需要注意的是随便地址由栈变为堆,但是存储的内容是字符串的地址,在未改变string值的时候是不会变化的。
-
te:这是一个指向栈内存tt的栈指针,在block内外都是指向tt的指针,所以没有变化;当将&h赋值给te时,te就是指向h的指针,此时与&h一样。
-
&te:相信到这里大家也能想到&te的变化了,&te是指针te的存储地址,block外是在栈中,block内则被拷贝到了堆中,&te其实就是结构体中的成员te的首地址,代码te=&h;的赋值只是改变了&te所在内存的内容,即修改了结构体中成员变量te的值,所以&te没有变化。
block在拷贝到堆上的时候,会将引用到的在栈上的变量全部拷贝到堆上,因为栈内存由系统释放对于堆block的引用来说可能造成引用到已释放内存,所以在栈上的引用都会copy到堆上,这样在arc下这些变量的释放就由引用计数控制了。
网友评论