邪恶的member-function pointer

谁都用过指针。

更别说我和你了。

但是当member-function pointer出现的时候,

你还是要跪倒!

嗯……今天耗了些精力d掉一个老bug。症状是这样的,从某人处拷贝来的static lib链接到应用程序之后,lib里面的函数尝试执行应用程序传过去的callback,结果没反应。

传入的callback是一个包含了某member-function pointer(MFP)的结构体,大致定义如下:

typedef void (Object::*FunctionPtr)(void);

struct Callback {
char * szName;
FunctionPtr fp;
}

库函数大致为这个样子:

void foo(Callback* ptr, Object* pObj) {

if(pObj && ptr->fp)
(pObj->*(ptr->fp))(); //怎么这么难看,我写对了伐……

}

开始以为是什么参数错误,后来惊奇的发现在本地重新编译一次static lib,再链接就一切正常僚。既然这样肯定是编译出来的asm有问题,略去痛苦的过程,直接进入主题,一路跟过去发现了不同。在错误的lib中,ptr->fp的地址是ptr+0x8h,而正确的lib里面是ptr+0x10h。于是去看了一下我本地编译出来的sizeof(Callback),猜猜是多少?

答案是32。

为啥这么大啊,于是又求了下sizeof(fp)。猜猜看这次又是多少?

答案是16。

本来以为fp至多再存个虚表,更何况我这个是non-virtual的,不要虚表,结果没想到搞出这么大一坨。这样看来,因为要对齐所以整个Callback结构体被搞成了32字节,这个char*可真是浪费呢。由此推出错误的lib里面是把fp当做8byte大小来储存的,所以ptr->fp就是ptr+0x8h。(好吧,这一段的分析在事后证明是错的,fp总是16byte,出错是因为错误的对齐规则,前者错误的使用了#pragma pack(16)的规则,邪恶的vc6.0 sp5……)

比较巧合的是,由于传入的Callback*来自一个储存在mem pool里的数据,而这个mem pool是由自己的内存分配器来管理的,一开始会把pool里的数据全部写0,所以错误的指针恰好指向了一段为0的内存,阴差阳错的通过了if判断,所以没有报access violation。

最重要的是为啥会有这么大的fp?放狗查到一篇文章,《Member Function Pointers and the Fastest Possible C++ Delegates》,原来这么复杂。其实我以前还用过这个fastdelegate,但是完全没注意到文章中有个描述不同编译器下的member-function pointer大小的表格。真是热闹啊,从4到20无奇不有……回头看看我的环境,应该是由于MSVC的编译选项不同导致了大小不同。具体是被当做那种情况处理了还得再仔细研究下……

裸用member-function pointer真是罪孽啊,还是封一层比较好。比如boost::bind那种东西……

嗯,总之是爽了。

2 thoughts on “邪恶的member-function pointer”

  1. F威武啊!
    这个问题还确实是挺诡异的,以前用lib都是自己编译,还真没遇见过这问题=。=

Leave a Reply

Your email address will not be published. Required fields are marked *