摘要:
1. 首先通过PEview工具观察一般的代码编译链接过程;
2. 通过PEview工具和IDA工具验证类模板函数的实例化过程;
正文:
首先,看看下面的简单代码例子:
文件1,Main.cpp:
---------------------------
#include “play.h” #include <stdlib.h> int main() { play(); system("pause"); return 0; }
文件2:play.h:
--------------------------------
#ifndef _play_h_ #define _play_h_ void play(); #endif
文件3:play.cpp:
-------------------------------
#include <stdio.h> void play(){ Printf(“I am the play\n”); }
上面三个文件存放在vs2008一个工程文件夹下面,按照这种方式生成的话,采用PEview查看main.obj的话,如下:
第二种写法,我们把play和main整合到一个文件中:
main.cpp:
-------------------------------------
void play(){ printf("i am the play"); } int main() { play(); system("pause"); return 0; }
对上述代码进行编译,并且采用PEview查看生成的obj文件的话,可以发现:
对比上述的两者,可以发现前后少了一个.text标志,可以猜想得知:
有代码定义的话,那么play这个会有Section Number的.text标志;
如果没有定义的话,只是一个引用的话,那么play这个没有对应的标志;
小结:经过上面的实验性代码,我们知道每个.cpp文件会编译成为对应名字的.obj文件;
如果某个.cpp中拥有某个函数的定义代码时,那么通过PEview查看的话,可以发现其对应的
名字的Section Number是.text;反之,某个.cpp中只是调用某个函数,而没有定义的话,那么
用PEview查看只能看到名字(如上的?play@@YAXXZ),但是对应的Section Number为空;
有了上面的经验,我们来看看类的函数的实例化过程。我们编译下面几个文件中的代码:
文件1:test.h:
template<class T> class A { public: void f(); //这里只是个声明 };
文件2:test.cpp:
#include "test.h" #include <stdio.h> template<class T> void A<T>::f() //模板的实现,但注意:不是具现 { printf(" in test\n"); }
文件3:main.cpp:
int main() { A<int> a; a.f(); system("pause"); return 0; }
在vs2008中F7编译上述文件,发现如下的错误:
call_template.obj : error LNK2019: 无法解析的外部符号"public: void __thiscall A<int>::f(void)" (?f@?$A@H@@QAEXXZ),该符号在函数_main 中被引用
也就是函数A<int>::f(void)在call_template.cpp中被引用,但却没有在其他obj文件中被定义。按照我们的设想,该函数的定义应该会出现在test.obj中,
通过PEview.exe查看test.obj文件,并没有发现?f@?$A@H@@QAEXXZ这个符号。这也意味着test.obj中关于A<T>::f()的代码并没有编译。
网上查找后,得知模板并不是直接编译为可执行代码,需要经过具象化的过程,然后才是代码的编译。根据c++标准,采用惰性具象化的机制,
即如果在实现该模板的.cpp文件中没有用到模板的具现体时,编译器就懒得去具现化。
作为验证,假设我们在test.cpp文件中增加相应的具现体代码,如下:
#include "test.h" #include <stdio.h> template<class T> void A<T>::f() //模板的实现,但注意:不是具现 { printf(" in test\n"); } void showme() { A<int> b; b.f(); }
此处的showme函数对模板进行具现化,编译器看到该具现体时,完成void A<int>::f()的编译。通过PEview查看test.obj,可以发现:
在.text段,出现?f@?$A@H@@QAEXXZ代号的函数实现体。如此main函数在引用时,link函数就可以在test.obj中找到对应的实现。
解决上述模板编译问题的一个基本举措: 将test.h和test.cpp合在一起,也即模板的声明和其对应的实现应该放在一起。
如果采用IDA来查看A<int>::f()有没有被定义,就更直接了:
如上,在IDA 6.1版本中,打开test.obj文件后,直接可以在标签Exports中查看其导出的符号。