看到运行结果了吗?
BEGIN copy constructor destructor END destructor
请注意BEGIN和END之间的输出:一个"copy constructor"和一个"destructor",现在让我们改变一下刚才那个编译选项,改成"Common Language Runtime Support(/clr)"[1],然后再编译,运行,看到运行结果了吗?
BEGIN copy constructor copy constructor destructor destructor END destructor
竟然有了两个"copy constructor"和两个"destructor",在BEGIN和END之间明明只有一个函数调用,为什么会有两个copy constructor?这个就是由Double Thunking 引起的问题[3]。(题外话:以后各位面试C++职位的时候要看清题目了,在不同的编译环境下,有很大的不同)
当我们使用/clr选项(不是/clr:pure)进行编译的时候,一个托管函数(managed function),会导致编译器生成一个托管的入口点(managed entry point)和一个原生的入口点(native entry point),这样可以使得托管函数既可以被托管代码调用,也可以被原生代码调用。但是,当一个原生的入口点存在的时候,它将成为所有调用的入口点。也就是说如果调用者是托管的,它还是会先去调用原生入口点,然后原生的入口点再去调用托管的入口点,这就意味着调用了两次函数入口点(Double Thunking)。
为了解决这个问题,VS2005中引入了__clrcall调用约定,它表明一个函数只能被托管代码调用,这样编译器就不会生成原生的入口点了,也就不会有Double Thunking问题了。让我们把
struct S {
virtual void f(T t) {};
} s;
改成
struct S {
virtual void __clrcall f(T t) {};
} s;
然后编译运行,是不是就没有Double Thunking问题了?
VC2005里还有一个编译选项"Pure MSIL Common Language Runtime Support (/clr:pure)",它会把所有的函数都按照__clrcall调用约定来编译。
7. Naked 函数调用
这是VC 里一种给高级用户使用的调用约定,它实际上就是没有规范,用户可以通过内嵌汇编来实现任意想要得调用约定。由于我们平时编程时基本上不会去使用它,所以我在这里不再展开了。可以参考[2]。
我们已经迎来了64bit时代,虽然大多数人装的操作系统还是32bit,用的还是32bit的软件,玩的还是32bit的游戏,但是我们的技术知识应该开始向64bit延伸了。调用约定在64bit的CPU上有些变化,总体来说64bit的调用约定和__fastcall类似,主要通过寄存器来传递参数。大家可以在intel网站上得到帮助信息。
参考文献:
[1] Common Language Runtime Compilation. http://msdn2.microsoft.com/en-us/library/k8d11d4s(VS.80).aspx
[2] Naked Function Calls. http://msdn2.microsoft.com/en-us/library/5ekezyy2(VS.80).aspx
[3] Double Thunking. http://msdn2.microsoft.com/en-us/library/ms235292(VS.80).aspx