VS2013 - C++碰到一个诡异的内存访问错误,很怀疑是 VS 的问题,要被逼疯了···求提点 T_T

2015-07-26 14:33:20 +08:00
 acros
先交代下环境吧。个人在做一个游戏的小功能(算是练习吧),用的cocos2d-x引擎,前些天从3.6版本升级到3.7(引擎内部好像有内存管理相关的改动),出现了一个奇怪的bug。
平台是win7 64 + VS 2013 community updater 5。 工程target都是x86 machine

下午再去xcode下搭建一个试试去,看看是不是ide的问题。

就是这个
https://github.com/acros/cocos2dx_qte

问题是这样的:
cocos2d-x里有个类叫Scene,我派生了class MainGameScene : public Scene,派生类中成员变量就加了两个RefPtr指针mUiLayer和mGameLayer。(注:RefPtr是cocos2d-x一个轻量级的shared_ptr类似存在)

通过VS的工具输出,确认Scene和MainGameScene的内存布局如下(这里就给出类末尾一段内容):
1> 708 | | _physicsWorld
1> 712 | | _navMesh
1> 716 | | _navMeshDebugCamera
1> | +---
1> 720 | ?$RefPtr@VLayer@cocos2d@@ mGameLayer
1> 724 | ?$RefPtr@VLayer@cocos2d@@ mUiLayer
1> +---

_navMesh和_navMeshDebugCamera是Scene类最后两个成员变量。
720偏移之前都是Scene的,这个表是正确的。

这个工程里面,cocos2d-x引擎部分是导出成一个dll的,然后主项目去加载它,MainGameScene这个类是在我自己的项目里面创建的。然而将一个指向MainGameScene的Scene*类型传到引擎代码中时,对于scene*内存的读取就发生了诡异的变化!
见下图:


可以看到:scene*的地址是0x060BBC88,加上前面计算的720偏移量,0x060BBF58地址应该是mGameLayer的地址,实际这时通过scene*访问到_navMesh变量时,取到的是mGameLayer地址!!!

图中下方窗口还可以看到,我加了个类型转换后再访问,地址就对了····>_<

(*((qte.exe!cocos2d::Scene*)(&(*((qte.exe!MainGameScene*)(scene))))))._navMesh
和&(scene)->_navMesh 取到的地址差 8 ??????

看起来虚表并没有被破坏掉,而且编译器计算类内成员offset时,这个offset怎么会算错,个人完全没头绪啊····(类成员地址不是类地址+offset计算的吗?)

注:scene*指针只在项目dll工程里面访问才会出现这个错误的偏移计算,我之前想过是不是项目配置错了,但问题是,什么东西配置错了会导致这种错误呢?!
2091 次点击
所在节点    C
26 条回复
mljack
2015-07-27 00:06:22 +08:00
在主程序和dll中分别把 每个scene成员的偏移都printf出来比比,应该就能看出来了
#define offs(s,m) (size_t)&(((s *)0)->m)

不行就合并到一个工程看看还有问题没
secondwtq
2015-07-27 03:26:06 +08:00
我个人倾向于@mljack 的观点。我最近恰好被这个问题坑得很惨(因为库和客户程序都是 Xcode 下类似的配置编译,所以并没有对齐问题,但是细节上有出入)。

我并不是非常熟悉调试的那一套理论(尤其是 VS 下面调试),我认为楼主可以尝试探索周围的成员变量地址,看看到哪里的时候两边的 offset 是一致的。

从截图中可以注意到:

`(*((qte.exe!cocos2d::Scene*)(&(*((qte.exe!MainGameScene*)(scene))))))._navMesh`,使用的应该是客户程序的偏移量。

`&(scene)->_navMesh` 貌似使用的是 dll 里的偏移量。这个调试信息有没有保存我并不清楚,是不是跟图中现在貌似正处于 dll 中代码的 context 下也不清楚。
forcecharlie
2015-07-27 10:20:34 +08:00
@nozama 构建系统一般只会检测 源文件的时间戳,然而,不一定检测头文件的时间戳,只有当 目标的时间戳比依赖的时间戳旧时,构建才会再一次发生,重新生成不是这样。
secondwtq
2015-07-27 14:56:48 +08:00
@forcecharlie 此话怎讲?一般情况下,某个 Compilation Unit (.c/cpp) 所 include 的 header 应该算是其依赖吧,那么这些 header 中再次引入的其他 header 算不算呢?如果先改动了 header 再改动 cpp(这样 cpp 的时间戳是最新的),是否会触发编译呢?
forcecharlie
2015-07-27 22:53:55 +08:00
@secondwtq 构建文件并没有解析 include 的能力,以 Makefile 为例,最好在依赖中显式的指明 头文件,如果一个构建系统并没有将头文件的添加到依赖,那么头文件的修改也不会有新的编译响应,当然,一些构建系统会根据文件 Hash 来检测,这些就需要存储到一个数据库文件或者是缓存文件。改动了 cpp 基本上都会触发编译。

Hello.obj:Hello.cpp Hello.h
cl -c Hello.cpp
secondwtq
2015-07-27 23:58:15 +08:00
@forcecharlie 这倒是,Makefile 这种比较直接的底层工具不会去检测这种特定的东西。

高级的工具貌似有专门去解决,我一般用 CMake,在 generate 的过程中会提取出 header 的依赖(据说是根据编译器提供的信息)。Xcode 也会自动触发编译,VS 大概不会没有。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/208440

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX