Block存储区域

Block存储区域

首先,需要引入三个名词:

● _NSConcretStackBlock

● _NSConcretGlobalBlock

● _NSConcretMallocBlock

正如它们名字显示得一样,表明了block的三种存储方式:栈、全局、堆。block对象中的isa的值就是上面其中一个,下面开始说明哪种block存储在栈、堆、全局。

------------【要点1】:全局block------------

● 定义在函数外面的block是global类型的

● 定义在函数内部的block,但是没有捕获任何自动变量,那么它也是全局的。比如下面这样的代码

[objc] view plaincopy

  1. typedef int (^blk_t)(int);
  2. for(...){
  3. blk_t blk = ^(int count) {return count;};
  4. }

虽然,这个block在循环内,但是blk的地址总是不变的。说明这个block在全局段。注:针对没有捕获自动变量的block来说,虽然用clang的rewrite-objc转化后的代码中仍显示_NSConcretStackBlock,但是实际上不是这样的。下图可以证明该类型的block是全局的。Xcode5.1.1调试结果

无论ARC与否,上图控制台输出是 <__NSGlobalBlock__: 0x10000f280>,这可能是编译器的优化,本人推测,没有求证。所以用clang的-rewrite-objc是不准确的。

------------【要点2】:栈block--------------

这种情况,在非ARC下是无法编译的,在ARC下可以编译

[objc] view plaincopy

  1. typedef void (^block_t)() ;
  2. -(block_t)returnBlock{
  3. __block int add=10;
  4. return ^{printf("add=%d\n",++add);};
  5. }

这是因为:block捕获了栈上的add自动变量,此时add已经变成了一个结构体,而block中拥有这个结构体的指针。即如果返回block的话就是返回局部变量的指针。而这一点恰是编译器已经断定了。在ARC下可以编译过,是因为ARC使用了autorelease了。

再说一个场景:

[objc] view plaincopy

  1. -(block_t)returnBlock{
  2. __block int add=10;
  3. block_t blk_h =^{printf("add=%d\n",++add);};
  4. return blk_h;
  5. }
  6. block_t bb = [self returnBlock];
  7. bb();

这段代码,只是使用了一个自动block变量,可以编过,但是造成程序崩溃了。
如果在返回block的时候加上copy,可以输出正确的数值11

------------【要点3】:堆上的block ----------------

有时候我们需要调用block 的copy函数,将block拷贝到堆上。看下面的代码:

[objc] view plaincopy

  1. -(id) getBlockArray{
  2. int val =10;
  3. return [NSArray arrayWithObjects:
  4. ^{NSLog(@"blk0:%d",val);},
  5. ^{NSLog(@"blk1:%d",val);},nil];
  6. }
  7. id obj = getBlockArray();
  8. typedef void (^blk_t)(void);
  9. blk_t blk = (blk_t){obj objectAtIndex:0};
  10. blk();

这段代码在最后一行blk()会异常,因为数组中的block是栈上的。因为val是栈上的。解决办法就是调用copy方法。这种场景,ARC也不会为你添加copy,因为ARC不确定,采取了保守的措施:不添加copy。所以ARC下也是会异常退出。

---------------------【要点4】copy的使用-----------------------------------

不管block配置在何处,用copy方法复制都不会引起任何问题。

在ARC环境下,如果不确定是否要copy block尽管copy即可。ARC会打扫战场。

【注意】:

● 在栈上调用copy那么复制到堆上

● 在全局block调用copy什么也不做

● 在堆上调用block 引用计数增加

------------------【对《Objective-C 高级编程》的挑战】-----------------------

笔者用Xcode 5.1.1 iOS sdk 7.1 编译发现:并非《Objective-C 高级编程》这本书中描述的那样,-rewrite-objc这个命令转化的中间代码,并不可靠。

block在ARC和非ARC有巨大差别:下面笔者用两种方式来验证:

1.通过Xcode调试结果,附图

2.通过变量的地址 int val肯定是在栈上的,我保存了val的地址,看看block调用前后是否变化。输出一致说明是栈上,不一致说明是堆上。

【第一个方法】,最直观.代码如下,很简单。block捕获了变量val(无论val是否是__block)

[objc] view plaincopy

  1. -(void) stackOrHeap{
  2. __block int val =10;
  3. blkt1 s= ^{
  4. return ++val;};
  5. s();
  6. blkt1 h = [s copy];
  7. h();
  8. }

在非ARC和ARC下,调试结果如下:

可以看到非ARC下一个是stack一个是Malloc。ARC下都是Malloc

【第二个方法】,声明一个局部变量指针。通过指针来看

[objc] view plaincopy

  1. typedef int (^blkt1)(void) ;
  2. -(void) stackOrHeap{
  3. __block int val =10;
  4. intint *valPtr = &val;//使用int的指针,来检测block到底在栈上,还是堆上
  5. blkt1 s= ^{
  6. NSLog(@"val_block = %d",++val);
  7. return val;};
  8. s();
  9. NSLog(@"valPointer = %d",*valPtr);
  10. }

在ARC下>>>>>>>>>>>该block被会直接生成到堆上了。看log: val_block = 11 valPointer = 10

在非ARC下>>>>>>>>>该block还是在栈上的。 看log:val_block = 11 valPointer = 11

调用copy之后的结果呢:

[objc] view plaincopy

  1. -(void) stackOrHeap{
  2. __block int val =10;
  3. intint *valPtr = &val;//使用int的指针,来检测block到底在栈上,还是堆上
  4. blkt1 s= ^{
  5. NSLog(@"val_block = %d",++val);
  6. return val;};
  7. blkt1 h = [s copy];
  8. h();
  9. NSLog(@"valPointer = %d",*valPtr);
  10. }

在ARC下>>>>>>>>>>>无效果。 val_block = 11 valPointer = 10
在非ARC下>>>>>>>>>确实复制到堆上了。 val_block = 11 valPointer = 10

----------------【总结】-----------------

用这个表格来表示。捕获变量包括仅仅读取变量和__block这种写变量,两种方式(其实结果是一样的)

在ARC下:似乎已经没有栈上的block了,要么是全局的,要么是堆上的

在非ARC下:存在这栈、全局、堆这三种形式。

时间: 2024-10-14 15:06:11

Block存储区域的相关文章

block存储区域——如何验证block在栈上,还是堆上

block存储区域 这就需要引入三个名词: ● _NSConcretStackBlock ● _NSConcretGlobalBlock ● _NSConcretMallocBlock 正如它们名字说的那样,说明了block的三种存储方式:栈.全局.堆. [要点1]定义在函数外面的block是global的:另外如果函数内部的block,但是没有捕获任何自动变量,那么它也是全局的.比如下面这样的代码: typedef int (^blk_t)(int); for(...){ blk_t blk

Block存储域

第一.在OC中允许block改写值的变量有三种: 静态变量 全局变量 静态全局变量 code示例如下所示: int global_val=1; staticint static_global_val=2; int main(int argc,const char * argv[]) { @autoreleasepool { static int static_val=3; void (^blk)(void)=^{ global_val *=2; static_global_val+=3; sta

C/C++程序内存的各种变量存储区域和各个区域详解

转自 https://blog.csdn.net/jirryzhang/article/details/79518408 C语言在内存中一共分为如下几个区域,分别是: 1. 内存栈区: 存放局部变量名:2. 内存堆区: 存放new或者malloc出来的对象:3. 常数区: 存放局部变量或者全局变量的值:4. 静态区: 用于存放全局变量或者静态变量:5. 代码区:二进制代码.知道如上一些内存分配机制,有助于我们理解指针的概念. C/C++不提供垃圾回收机制,因此需要对堆中的数据进行及时销毁,防止内

存储区域

前言:类型定义后,存储在哪里?变量定义后存储在哪里?程序代码存储在哪里? 1. 全局数据区和程序代码区 类型定义后,存在内存的只读区域,不可见,无法取得地址.类变量记录的是地址偏移,与对象绑定才能取得地址. 变量: 全局变量存在全局数据区,全局静态变量存在全局数据区的全局静态变量段.(两者相临或相差几个字节). 名空间变量存在全局数据区,和全局变量同一存储区域,名空间静态变量存在全局数据区的名空间静态变量段,和全局静态变量同一存储区域. 类变量名称列表中记录的是地址偏移,和对象绑定才能取得地址.

C++内存存储区域的划分

C++内存存储区域划分 面试笔试常考 内存中对数据的存储不是杂乱无章的,而是有相应的划分,根据数据类型分门别类安放到相应的位置. 存储的区域由最高存储地址到最低存储地址依次为: 命令行参数区:命令行参数和环境变量: 栈区(stack):指那些由编译器需要的时候分配,在不需要的时候自动清除的变量的存储区域.里面的变量通常是函数的参数值.局部变量等值,其操作方式类似于数据结构中的栈. 堆区(heap):动态分配内存.一般由程序员分配释放,若程序员不释放掉,在程序结束后,操作系统可能会自动回收.它与数

C语言5种存储区域

C语言5种存储区域 转发至:http://www.mamicode.com/info-detail-927635.html 系统为了管理内存 把内存划分了几个区域 1> 栈区 栈区之中的数据在栈区之中以栈的形式进行存储. 栈区的特点:数据是先进后出, 放在栈区里面存放的是局部变量.(例如定义在函数内部的变量) 栈区之中的数据(局部变量)的作用范围过了之后,系统就会回收自动管理栈区的内存(分配内存 , 回收内存),不需要开发人员来手动管理 2> 堆区 高效的使用内存 这里的内存可以由程序员自己手

浅谈个人对存储区域网络SAN的理解

存储区域网络SAN,是一种通过将网络存储设备和服务器连接起来的网络,提供计算机和存储设备间的数据传输.其中,SAN是独立于服务器系统之外的,拥有近乎无限的存储能力,通过利用光纤作为传输媒介,实现了高速共享,它主要由服务器,外部存储设备,服务器适配器,集线器,交换机和网络,存储管理工具等组成.SAN的优点都体现在以下几点: (1)管理方便 可通过一台SAN给多个服务器提供存储,存储管理方便. (2)扩展性强 SAN由多个磁盘组成,用户可自由增加磁盘,服务器等,可提供海量空间.而逻辑上,它又可以被分

Block 在不同情况下的变量存储区域

一.如果只使用全局或静态变量或不使用外部变量, 那么Block块的代码会存储在全局区;二.如果使用了外部变量, 在ARC中, Block块的代码会存储在堆区;                                     在MRC中, Block快的代码会存储在栈区; 三.block默认情况下不能修改外部变量, 只能读取外部变量:                                  在ARC中, 外部变量存在堆中, 这个变量在Block块内与在Block块外地址相同;  

OC 中的block存储位置

一.block块的存储位置(block块入口地址):可能存放在2个地方:代码区.堆区(程序分5个区,还有常量区.全局区和栈区). 详细介绍: 情况1:代码区 不访问处于栈区的变量(例如局部变量),且不访问处于堆区的变量(例如alloc创建的对象).也就是说访问全局变量也可以. /** 没有访问任何变量 */ int main(int argc, char * argv[]) { void (^block)(void) = ^{ NSLog(@"==="); }; block(); }