最近升级了Xcode 7 GM,发现GitDiff 插件不工作了。于是自力更生翻源码找bug。虽然最后发现原因是之前用Xcode-beta 时xcode-select 到了beta 版,和GitDiff 并没有什么关系,但看源码时发现一对看起来很牛逼的函数:setjmp/longjmp。好奇心大起,查阅了一下他们究竟为何物。
简单来说,setjmp/longjmp 类似goto,但牛逼的多,因为setjmp/longjmp 可以在函数间“跳来跳去”。
不得不吐槽这对函数名,实在太不直观了。。。
仅仅这么个作用的话,貌似没什么使用的必要,那GitDiff为何这么兴师动众的调用这么底层的函数呢?翻看了一下GitDiff 的commit 记录,才知道原来还有个主要人物--signal
。
概括GitDiff 的做法,就是通过setjmp
保存好上下文,用signal
函数来截获可能出现的SIGPIPE
,然后在SIGPIPE
出现时通过longjmp
返回出错之前的代码,并通过返回值来避开出错代码,继续往下走。
换句话说,就是实现了一个简单的try-catch机制。这也是setjmp/longjmp/signal互相配合的一个“常见”用法。
关于这个
SIGPIPE
,简单Google 了一下,貌似用popen
时还是挺常见的,一般都需要一套同步机制来保证popen
和pclose
分开来说
- 用
setjmp
保存当前上下文环境到参数(jmp_buf
类型)里,jmp_buf
的细节和系统具体实现相关,我也没再追下去。其返回值的规则是:第一次调用时返回0,表示是从setjmp
返回的;之后,如果longjmp
被调用,那么程序会根据保存的上下文环境(之前保存的jmp_buf
类型的变量,也是longjmp
的第一个参数),再次来到setjmp
这里,并返回一个由longjmp
带过来的值(longjmp
的第二个参数),程序可以通过setjmp
的返回值来改变流程。 - 调用
signal
函数安装SIGPIPE
信号的处理函数。通过man
知道signal
的返回值是“当前的对该信号(这里也即SIGPIPE
)的处理函数”,也就是默认的处理函数,保存这个值很有必要,因为需要在最后进行恢复。 - 如果
SIGPIPE
被触发,那么在之前安装的信号处理函数里调用longjmp
。其第二个参数就是返回到setjmp
时的值,这里只要给一个不是0的值,就可以标记为“出错”。
虽然最后没有找出GitDiff 的bug,但是收获颇丰:)