iOS

iOS:OC变量的存储细节

2018-05-17  本文已影响0人  stonly916

在iOS或者其他系统中,程序运行中的存储根据功能分类,基本都有这几个分类:

这里说的静态常量与是否有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,然后看各个变量,

block在拷贝到堆上的时候,会将引用到的在栈上的变量全部拷贝到堆上,因为栈内存由系统释放对于堆block的引用来说可能造成引用到已释放内存,所以在栈上的引用都会copy到堆上,这样在arc下这些变量的释放就由引用计数控制了。

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

上一篇 下一篇

猜你喜欢

热点阅读