想想如果我们自己要做编译器(compiler)和连接器(linker),当然希望编译连接运行得越快越好,同时也希望产生的二进制代码也是又快又小,上帝是公平的,鱼与熊掌不可兼得,所以我们自然想到用两种build方式,一种Release,编译慢一些,但是产生的二进制代码紧凑精悍,一种Debug,编译运行快,产生的代码臃肿一点没关系,Debug版本嘛,就是指望程序员在开发的时候反复的build,为了不浪费程序员的时候,要想尽办法让编译连接速度变快。
假如一个程序有连续两个 foo 和 bar ( 所谓连续,就是他们编译连接之后函数体连续存放 ) , foo 入口位置在 0x0400 ,长度为 0x200 个字节,那么 bar 入口就应该在 0x0600 = 0x0400+0x0200 。程序员在开发的时候总是频繁的修改 code 然后 build ,假如程序员在 foo 里面增加了一些内容,现在 foo 函数体占 0x300 个字节了, bar 的入口也就只好往后移 0x100 变成了 0x0700 ,这样就有一个问题,如果 foo 在程序中被调用了 n 次,那么 linker 不得不修改这 n 个函数调用点,虽然 linker 不嫌累,但是 link 时间长了,程序员会觉得不爽。所以 MSVC 在 Debug 版的 build ,不会让各个函数体之间这么紧凑,每个函数体后都有 padding (全是汇编代码 int 3 ,作用是引发中断,这样因为古怪原因运行到不该运行的 padding 部分,会发生异常),有了这些 padding ,就可以一定程度上缓解上面提到的问题,不过当函数增加内容太多超过 padding ,还是有问题,怎么办呢? MSVC 在 Debug build 中用上了 Incremental Link Table , ILT 其实就是一串 jmp 语句,每个 jmp 语句对应一个函数, jmp 的目的地就是函数的入口点,和没有 ILT 的区别是,现在对函数的调用不是直接 call 到函数入口点了,而是 call 到 ILT 中对应的位置,而这个位置上什么也不做,直接 jmp 到函数中去。这样的好处是,当一个函数入口地址改变时,只要修改 ILT 中对应值就搞定了,用不着修改每一个调用位置,用一个冗余的 ITL 把时间复杂度从 O(n) 将为 O(1) ,值得,当然 Debug 版的二进制文件会稍大稍慢, Release 版不会用上 ILT 。什么是Incremental Link Table[转]
最新推荐文章于 2023-04-01 16:05:32 发布