一般,编译器采取最差的,而且一直使用最普通的形式。比如对于下面这个结构:
// Borland (缺省设置) 和Watcom C++.
struct {
FunctionPointer m_func_address;
int m_delta;
int m_vtable_index; //如果不是虚拟继承,这个值为0。
};
// Metrowerks CodeWarrior使用了稍微有些不同的方式。
//即使在不允许多重继承的Embedded C++的模式下,它也使用这样的结构!
struct {
int m_delta;
int m_vtable_index; // 如果不是虚拟继承,这个值为-1。
FunctionPointer m_func_address;
};
// 一个早期的SunCC版本显然使用了另一种规则:
struct {
int m_vtable_index; //如果是一个非虚拟函数(non-virtual function),这个值为0。
FunctionPointer m_func_address; //如果是一个虚拟函数(virtual function),这个值为0。
int m_delta;
};
//下面是微软的编译器在未知继承类型的情况下或者使用/vmg选项时使用的方法:
struct {
FunctionPointer m_func_address;
int m_delta;
int m_vtordisp;
int m_vtable_index; // 如果不是虚拟继承,这个值为0
};
// AIX (PowerPC)上IBM的XLC编译器:
struct {
FunctionPointer m_func_address; // 对PowerPC来说是64位
int m_vtable_index;
int m_delta;
int m_vtordisp;
};
// GNU g++使用了一个机灵的方法来进行空间优化
struct {
union {
FunctionPointer m_func_address; // 其值总是4的倍数
int m_vtable_index_2; // 其值被2除的结果总是奇数
};
int m_delta;
};
对于几乎所有的编译器,delta和vindex用来调整传递给函数的this指针,比如Borland的计算方法是:
adjustedthis = *(this + vindex -1) + delta // 如果vindex!=0
adjustedthis = this + delta // 如果vindex=0
(其中,“*”是提取该地址中的数值,adjustedthis是调整后的this指针——译者注)
Borland使用了一个优化方法:如果这个类是单一继承的,编译器就会知道delta和vindex的值是0,所以它就可以跳过上面的计算方法。
GNU编译器使用了一个奇怪的优化方法。可以清楚地看到,对于多重继承来说,你必须查看vtable(虚拟函数表)以获得voffset(虚拟函数偏移地址)来计算this指针。当你做这些事情的时候,你可能也把函数指针保存在vtable中。通过这些工作,编译器将m_func_address和m_vtable_index合二为一(即放在一个union中),编译器区别这两个变量的方法是使函数指针(m_func_address)的值除以2后结果为偶数,而虚拟函数表索引(m_vtable_index_2)除以2后结果为奇数。它们的计算方法是:
adjustedthis = this + delta
if (funcadr & 1) //如果是奇数
call (* ( *delta + (vindex+1)/2) + 4)
else //如果是偶数
call funcadr
(其中, funcadr是函数地址除以2得出的结果。——译者注)
Inter的Itanium编译器(但不是它们的x86编译器)对虚拟继承(virtual inheritance)的情况也使用了unknown_inheritance结构,所以,一个虚拟继承的指针有20字节大小,而不是想象中的16字节。
// Itanium,unknown 和 virtual inheritance下的情况.
struct {
FunctionPointer m_func_address; //对Itanium来说是64位
int m_delta;
int m_vtable_index;
int m_vtordisp;
};
我不能保证Comeau C++使用的是和GNU相同的技术,也不能保证它们是否使用short代替int使这种虚拟函数指针的结构的大小缩小至8个字节。最近发布的Comeau C++版本为了兼容微软的编译器也使用了微软的编译器关键字(我想它也只是忽略这些关键字而不对它们进行实质的相关处理罢了)。
Digital Mars编译器(即最初的Zortech C++到后来的Symantec C++)使用了一种不同的优化方法。对单一继承类来说,一个成员函数指针仅仅是这个函数的地址。但涉及到更复杂的继承时,这个成员函数指针指向一个形式转换函数(thunk function),这个函数可以实现对this指针的必要调整并可用来调用实际的成员函数。每当涉及到多重继承的时候,每一个成员函数的指针都会有这样一个形式转换函数,这对函数调用来说是非常有效的。但是这意味着,当使用多重继承的时候,子类的成员函数指针向基类成员函数指针的转换就会不起作用了。可见,这种编译器对编译代码的要求比其他的编译器要严格得多。
很多嵌入式系统的编译器不允许多重继承。这样,这些编译器就避免了可能出现的问题:一个成员函数指针就是一个带有隐藏this指针参数的普通函数指针。