Block探究:第二篇(Stack_Block)

原文地址:http://www.galloway.me.uk/2012/10/a-look-inside-blocks-episode-1/
如原作者发现有侵权行为可责令我在24小时之内删除,前提是你能看到。

这是我从编译器角度了解block内部实现的第二篇文章。在这篇文章里,我将会研究一下block在栈上的几种类型。


Block类型

第一篇文章里我们了解到,block中有一个isa指针指向了_NSConcreteGlobalBlock这个类,因为block结构体和block中的descriptor结构体在编译时就全部初始化完成了,所以我们可以看到里面的变量。block有几种不同的类型,每种类型都有与其关联的类,但是我们只需要考虑他们中常见的三种即可:
1._NSConcreteGlobalBlock是一种在编译期间就完全定义好的全局block,这类block没有捕获任何外部变量,例如一个空的block。
2._NSConcreteStackBlock是一种位于栈上的block,这种block在最终被copy到堆中之前一直都存储在栈上。
3._NSConcreteMallocBlock是一种位于堆上的block,在对block进行copy操作之后,它的引用计数会增加,知道引用计数减为0,这个block就会被释放。


有捕获范围的block

来看看下面的代码:

block捕获外部变量

名为foo的函数有一个参数,在block中调用foo函数的时候将外面的变量a捕获并传给函数,同样的,我们来看看在armv7架构下汇编的过程,相关代码如下:

block捕获外部变量汇编

这段汇编代码和第一篇中的一样,调用了block的invoke函数,接下来看看doBlockA函数:
doBlockA函数

这段和之前的比起来有点难度了,和之前的加载全局block不同,这里做了更多的事,虽然看起来有点恐怖,但认真看其实很容易看到它做了些什么。因为编译器没有对这些指令进行优化,所以我将要对这些代码在不改变原有功能的基础上重新整理一下,下面是整理后的代码:

整理后的doBlockA函数

主要的功能点如下:
1.一开始让r7入栈是为了避免r7的内容被覆盖,因为r7里面的内容在函数间调用时需要用到。lr(链接寄存器)保存了函数返回时要执行的下一条指令的地址。最后将栈指针保存在r7中。
2.从栈顶指针减去24个字节,也就是从栈中开辟出24字节用来存储数据。
3.这里的代码是为了对符号L__NSConcreteStackBlock$non_lazy_ptr进行寻址,跟pc有关,当代码最终被链接时不管这段代码在二进制文件中的什么位置计算机都能通过pc找到它,并将它存储在栈指针所指的内存地址中。
4.值1073741824被存储在栈顶指针+4的位置。
5.将0存储到栈指针 + 8 的位置。现在,将要发生什么可能已经变得逐渐清晰了——在栈上创建了一个Block_layout结构!到现在为止,已经设置了该结构的3个值:isa指针,flagsreserved值。
6.___doBlockA_block_invoke_0存储在栈指针+12的地址处,也就是block结构体的invoke属性。
7.___block_descriptor_tmp存储在栈指针+16的地址处,也就是block结构体的descriptor属性。
8.值128存储在栈指针+20的地址处,你可能发现了Block_layout结构体中只有5个值,并且这5个值已经都存储在栈中了,那么接下来栈中要存储什么?你会发现正是从外面捕获到的值128。所以这肯定就是存储block使用到的值的地方——在Block_layout结构尾部。
9.现在栈指针指向的是一个完整的block机构体,然后将栈指针存入r0中,然后调用runBlockA函数。(注意:在ARM EABI中r0通常用来存放函数的第一个参数)。
10.最后,让栈指针+24收回之前在栈中开辟的24字节的空间,接着将栈中的内容分别pop到r7pc中,pop回r7的就是函数一开始从r7push到栈中的内容,pc的值是函数开始时push到栈中的lr的值,这样在函数返回时程序可以继续执行下面的指令。

哇,如果你看到这的话,说明你太棒了!

最后一部分让俺们来看看block中的invoke函数和descriptor编译的结果。我希望他们要比第一篇中的global block的这两个属性简单。看代码:

invoke和descriptor

的确和第一篇中的没有什么差别,唯一不同的是descriptor中的size值的大小不同,现在是24而不是20,这是因为block捕获到一个整数所以block的大小变为了24,之前也看到在block创建的时候有额外的4个字节在block的尾部一起被存入了栈中。

在实际的block调用函数里,比如__doBlockA_block_invoke_0,我们能看到在调用___doBlockA_block_invoke_0函数中先从r0 + 20地址初开始读取了4个字节的数据到r0中,这额外的4个字节也就是block从外部捕获到那个整数,然后调用foo函数。


如果捕获的是一个对象类型呢?

我们需要考虑的下一个问题是如果block捕获的外部变量是一个对象类型比如NSString而不是一个整数,那么会发生什么,看看下面的代码:

捕获对象类型的block

doBlockA函数我就不讲了,和之前的一样,没有太多的变化。有点意思的是这个block的descriptor结构体指针:
descriptor

注意这边有两个函数指针:__copy_helper_block__destroy_helper_block。下面是这些函数的定义:
___copy_helper_block_和___destroy_helper_block_

我假设block被拷贝和销毁正是因为有这两个函数,那么他们一定会对block捕获的对象进行retain和release操作。拷贝函数接收两个参数(r0r1),而销毁函数接收一个参数。可以看出所有的拷贝和销毁任务都应该是由__Block_object_assign__Block_object_dispose两个函数完成的。这两个函数位于block的运行时代码中,是LLVM里面compiler-rt工程的一部分。

如果你想继续了解runtime中有关block的代码,可以去http://compiler-rt.llvm.org下载相关源代码。尤其要去看看runtime.c这个文件。


下一篇讲啥?

下一篇准备深入了解一下block在runtime中的Block_copy,看看它是如何工作的。这也能让我们能更好的理解上面在block捕获的外部对象时创建的__copy_helper_block__destroy_helper_block函数。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • 前言 Blocks是C语言的扩充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了这...
    小人不才阅读 3,750评论 0 23
  • 一、Objective-C发展史 Objective-C从1983年诞生,已经走过了30多年的历程。随着时间的推移...
    没事蹦蹦阅读 5,806评论 12 34
  • 《Objective-C高级编程》这本书就讲了三个东西:自动引用计数、block、GCD,偏向于从原理上对这些内容...
    WeiHing阅读 9,780评论 10 69
  • 1: 什么是block?1.0: Block的语法1.1: block编译转换结构1.2: block实际结构 2...
    iYeso阅读 821评论 0 5
  • 目录 Block底层解析什么是block?block编译转换结构block实际结构block的类型NSConcre...
    tripleCC阅读 33,127评论 32 388