问题描述
摘一段使用“桩代码”的场景,来自《程序员的自我修养》第一版第264页:当延迟载入的API第一次被调用时,由链接器添加的特殊的桩代码就会启动,这个桩代码负责对DLL的装载工作。然后这个桩代码通过调用GetProcAddress来找到被调用API的地址。
题主问题中所说的“stub”的用法,下面几个词是相似的:
- stub
- trampoline
- ricochet
- thunk
它们说的都是“一小块代码”,通常是有个caller要调用callee的时候,中间需要一些特殊处理的逻辑,就会用这种“小块代码”去做。有意思的是,这些词除了stub以外都跟“跳”有关系:
- trampoline:跳床
- ricochet:反弹,弹跳
- thunk:反弹时的声音(“锵”)
而“跳”正好生动的捕捉了这类stub的作用:它们并不是最终的调用目标,而是做一些简单的处理之后“跳”到真正的目标去。
很多时候这种“跳”都是一次性的:“跳”过去之后就不回来了。
题主提到的例子是动态链接库的延迟加载。还有许多其它类似的场景,举两个例子:
- 微软.NET的虚拟机CLR是一个纯JIT编译的执行系统(这里请暂时忽略NGEN⋯)。每个方法在第一次被调用的时候触发JIT编译。但是具体是如何“触发”的呢?CLR加载一个类型的时候会为该类型生成一个方法表(MethodTable),表里每一项是该类型里每个方法的入口地址。调用方法就通过该表来查询入口地址并跳转过去。在刚加载好的时候,方法表里的项都指向触发JIT编译的stub(MethodDesc::DoPrestub)。某个方法第一次被调用的时候,实际被调用到的就是这个prestub,它就会负责触发JIT编译,等到JIT编译完成后将MethodTable里对应的项改为指向编译好的代码的入口地址,然后跳转到那个入口地址开始执行第一次调用。后续的调用从方法表里查到的地址是真正的入口,就不会再进到prestub了。这里有个讲解写得不错:.NET Just in Time Compilation and Warming up Your System
- Haskell语言支持惰性求值(lazy evaluation)——表达式的值在还没被真正用到的时候可以先不求值。在GHC里这通过thunk(unevaluated thunk)来实现。一个Thunk持有求值所需的自由变量和运算,就跟闭包类似;只不过thunk求值完之后会把自己替换为求好的值:http://www.scs.stanford.edu/11au-cs240h/notes/ghc-slides.html#(55)