美文网首页iOS
iOS:OC变量的存储细节

iOS:OC变量的存储细节

作者: stonly916 | 来源:发表于2018-05-17 12:02 被阅读0次

在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下这些变量的释放就由引用计数控制了。

参考
http://www.molotang.com/articles/2001.html

相关文章

网友评论

    本文标题:iOS:OC变量的存储细节

    本文链接:https://www.haomeiwen.com/subject/cggnrftx.html