C++ 总是给你惊喜

C++ 总是给你惊喜

今天把公司项目的cocos版本升级到3.0,顺便把build system也换成了CMake。结果升级完成后在link的时候报了个错

MyObject.cpp.o:(.rodata._ZTVN2sg8MyObjectE[_ZTVN2sg8MyObjectE]+0x32c): undefined reference to `non-virtual thunk to cocos2d::Sprite::getTexture() const'

回头看了一下Sprite 类,分明已经定义了getTexture函数。用nm命令查找了一下symbol

nm -po CCSprite.cpp.o | c++filt | grep getTexture

CCSprite.cpp.o:000017fa T non-virtual thunk to cocos2d::Sprite::getTexture() const

很好,确实已经定义了,再看一下使用的地方

nm -po MyObject.cpp.o | c++filt | grep getTexture

MyObject.cpp.o: U non-virtual thunk to cocos2d::Sprite::getTexture() const

编译MyObject的时候确实应该是看不到getTexture的定义的,但是链接的时候symbol应该能够被找到啊。这下就尴尬了。

回过头重新去看代码,这个时候走了些弯路,Sprite类是一个多重继承的类,所以一度怀疑是不是多重继承出了什么问题,折腾了好久,期间还看到一个有意思的现象,如果把Sprite的两个父类颠倒一下继承顺序,链接就通过了。这个时候感觉不像是代码的问题,问题应该还是在链接上,于是又重新导出了symbol看了一下

nm -po CCSprite.cpp.o | grep getTexture
CCSprite.cpp.o:000017fa T _ZThn512_NK7cocos2d6Sprite10getTextureEv

nm -po MyObject.cpp.o | grep getTexture
MyObject.cpp.o: U _ZThn516_NK7cocos2d6Sprite10getTextureEv

纳尼,为啥mangled出来的名字不一样啊!不过差别不大,准备查查这个数字是啥意思。

在这个网站itanium-cxx-abi.github.io 查到了gcc mangling的规则。Th 代表了non-virtual base class, 516 代表this指针需要的offset,n代表negative。 getTexture是Sprite第二个父类的虚函数,通过Sprite类型的指针对它进行调用时候需要对this指针进行调整,而此处的516就是需要进行调整的offset的大小,而根据gcc目前的机制,这个offset显然是受第一个父类的大小的影响,那么差的4个字节应该就是第一个父类里某个成员变量的大小。回过头重新去看第一个父类的代码,果然有一个成员变量是受某个宏开关的控制,如果宏是1则定义 这个变量,是0则不定义。这个时候答案就很显然了,在编译cocos lib 的时候这个宏默认是0,而编译游戏代码的时候这个宏被设成了1,结果导致两边mangled的名字不一样,进而导致链接错误。而最终发现游戏代码将宏设成1的原因是很搞笑的,本来以为如果编译时不加入该宏的定义,这个宏会被默认定义为0,然而事实证明我太naive了,cocos对这个宏的处理和我的理解正好相反,如果不定义它,则默认定义为1 ……

发布于 2017-05-26 13:46