VS2013 - C++碰到一个诡异的内存访问错误,很怀疑是 VS 的问题,要被逼疯了求提点 T_T - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
acros
V2EX    C

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

  •  
  •   acros 2015-07-26 14:33:20 +08:00 2481 次点击
    这是一个创建于 3733 天前的主题,其中的信息可能已经有所发展或是发生改变。
    先交代下环境吧。个人在做一个游戏的小功能(算是练习吧),用的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工程里面访问才会出现这个错误的偏移计算,我之前想过是不是项目配置错了,但问题是,什么东西配置错了会导致这种错误呢?!
    第 1 条附言    2015-07-26 22:07:39 +08:00
    确认xcode下也是一样的问题,洗清VS的嫌疑了。
    不过xcode的debug显示_navMesh = null,然而
    在 if(_navMesh) { do.. sth..}语句中还是进去了。

    唔估计得查查C++文档了我去
    第 2 条附言    2015-07-28 00:43:46 +08:00
    @forcecharlie
    @secondwtq
    @mljack
    @xdeng
    @bombless
    @endrollex
    @lcsky
    @jukka
    @XiXiLocked
    @finab

    谢谢各位的建议,查了两天终于查到了。
    把类内成员的offset在DLL内外输出了一遍,终于看到原因了
    事因非常蛋疼,是cocos2d-x生成的项目配置问题,特地生成了个新项目看了下。

    3.7版本里面加了个宏:CC_ENABLE_BULLET_INTEGRATION=1
    这是在cocos2d-x DLL项目的Presscessor Definition中配置的(不是直接写在头文件里),但是主项目中并没有继承这个宏,导致引用DLL时,主项目编译的头文件跳过了CC_ENABLE_BULLET_INTEGRATION相关的两个指针成员变量(就是8byte差别的来源了),所以DLL外进行Scene类内寻址改成员之后的变量全部出错。
    修改方法就是在主项目预定义宏里面加上CC_ENABLE_BULLET_INTEGRATION=1解决问题了。
    顺带在git上给cocos2d-x的同学写了个issue,估计他们会马上改过来吧。

    汗颜一个先。
    26 条回复    2015-07-27 23:58:15 +08:00
    jukka
        1
    jukka  
       2015-07-26 17:23:30 +08:00
    show me your code.
    XiXiLocked
        2
    XiXiLocked  
       2015-07-26 17:26:01 +08:00   1
    你看直接读navMesh是有值的,但是指针转换之后读的navMesh是NULL,说明转的有问题
    信口开河一把,
    scene的内存布局是
    scene::vptr
    scene::members ...
    MainGameScene的里面有并不继承于scene的虚函数,所以他的虚表插在了前面,于是布局大概是这样
    scene:vptr
    MainGameScene::vptr <--这里多了虚表指针的8
    scene::members ...
    MainGameScene::members ...
    上面说的没有把握,但是你可以看看之前的成员变量的地址验证一下
    acros
        3
    acros  
    OP
       2015-07-26 17:39:13 +08:00 via iPhone
    @jukka 上面git地址就是啊。
    acros
        4
    acros  
    OP
       2015-07-26 17:40:06 +08:00 via iPhone
    @XiXiLocked
    _navMesh应该是空的。mGaneLayer不是null
    acros
        5
    acros  
    OP
       2015-07-26 17:40:50 +08:00 via iPhone
    @XiXiLocked 我去验证下..
    finab
        6
    finab  
       2015-07-26 17:56:03 +08:00
    代码出问题
    我从不怀疑编译器或IDE,肯定是我的问题!
    就是这么自信 ~~~
    fo2w
        7
    fo2w  
       2015-07-26 18:04:52 +08:00
    虽然说vs确实有bug, 但我不相信大多数人碰得到
    Athrob
        8
    Athrob  
       2015-07-26 18:07:04 +08:00
    确定不是代码的问题?我相信vs
    nozama
        9
    nozama  
       2015-07-26 18:56:03 +08:00 via Android
    vs2012 bug像这种“明明是对的,结果就是不对”倒是遇到过几次,在2013和appcode都没问题。也有几次,是变量忘了初始化引起的bug,clang会自动初始化为0,VC却是随机值。
    lcsky
        10
    lcsky  
       2015-07-26 19:05:38 +08:00   1
    可能是遗漏了某些概念,我随便拍拍脑袋能想到相关的可能有:
    1、struct、class成员的默认内存对齐方式(#pragma pack)
    2、不清楚C++其实是没有ABI兼容性的

    后面这个问题会导致不同的C++编译器、编译器版本、甚至只是不同的编译参数生成的二进制代码不兼容。你看系统库都是纯C接口的发现没有?因为纯C有函数调用的ABI兼容性标准:stdcall、cdecl、fastcall。而C++是彻底没有ABI兼容性的,能“差不多对上”只是运气好,或者编译器版本、参数完全一样。

    所以重点恐怕是检查你dll和主程序的编译参数有何区别
    acros
        11
    acros  
    OP
       2015-07-26 19:14:52 +08:00 via iPhone
    @Athrob
    @nozama
    @fo2w
    我也认为代码出错可能性更大。 一般代码灵异问题都是内存初始化或者溢出一类导致的,这次是摸不着头脑了....从内存布局上看,没有被覆盖的问题....回头再查查vs类底层虚表有没错误,哎,没学好汇编和编译原理,到底层就非常吃力。
    husinhu
        12
    husinhu  
       2015-07-26 20:59:46 +08:00   1
    同意@lcksy 如果真是ABI不同或者编译选项不同,建议同一编译选项,加一层SceneAdaptor来decouple
    lingo233
        13
    lingo233  
       2015-07-26 21:06:32 +08:00
    我受不了了明明2015已经发布了竟然不升级,要被逼疯了(-_-)
    nozama
        14
    nozama  
       2015-07-26 21:29:20 +08:00   1
    也许还有一种可能, 也是vs2012的bug: 某个源文件会莫名其妙被copy一份, 然后无论怎么修改, 编译的仍是原来的那个文件.
    于是就可能出现: 头文件更新了, dll其实还是以前的, 然后按照偏移量访问某个字段, 实际上是访问到了另一个字段.
    endrollex
        15
    endrollex  
       2015-07-26 22:09:46 +08:00   1
    http://blog.csdn.net/smstong/article/details/24455371
    如果C++编译器不能根据类声明推算出类型转换时的指针调整方式时,如果使用了强制类型转换,那么编译器只是简单的默默无作为
    bombless
        16
    bombless  
       2015-07-26 23:18:23 +08:00   1
    以前见到有个人有类似的问题,貌似是同一个变量名在不同的命名空间用上了,然后链接的时候出错(或者是调试器显示的有问题,忘记具体情况了)
    mljack
        17
    mljack  
       2015-07-26 23:25:22 +08:00   1
    检查下dll和主程序的工程设置中预编译宏是否一致,感觉像 #ifdef xxx 造成的问题,c/c++编译器对于一些.h 和.lib里的class是否一致并不做完整的检查。

    仔细看了一下,应该是编译 dll 时没定义CC_USE_NAVMESH,而使用 dll 时又定义了CC_USE_NAVMESH造成的。
    tushiner
        18
    tushiner  
       2015-07-26 23:29:28 +08:00
    我开发的时候也是用网易云音乐听歌
    acros
        19
    acros  
    OP
       2015-07-26 23:29:33 +08:00
    @mljack 这个我确认过了。如果CC_USE_NAVMESH关闭掉,读取错误的地址还是偏移8(这时候就是_navMesh前面的变量读取错误了),应该不是具体变量造成的。
    xdeng
        20
    xdeng  
       2015-07-26 23:31:09 +08:00   1
    有个东西 叫 字节对齐
    mljack
        21
    mljack  
       2015-07-27 00:06:22 +08:00   1
    在主程序和dll中分别把 每个scene成员的偏移都printf出来比比,应该就能看出来了
    #define offs(s,m) (size_t)&(((s *)0)->m)

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

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

    从截图中可以注意到:

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

    `&(scene)->_navMesh` 貌似使用的是 dll 里的偏移量。这个调试信息有没有保存我并不清楚,是不是跟图中现在貌似正处于 dll 中代码的 context 下也不清楚。
    forcecharlie
        23
    forcecharlie  
       2015-07-27 10:20:34 +08:00
    @nozama 构建系统一般只会检测 源文件的时间戳,然而,不一定检测头文件的时间戳,只有当 目标的时间戳比依赖的时间戳旧时,构建才会再一次发生,重新生成不是这样。
    secondwtq
        24
    secondwtq  
       2015-07-27 14:56:48 +08:00
    @forcecharlie 此话怎讲?一般情况下,某个 Compilation Unit (.c/cpp) 所 include 的 header 应该算是其依赖吧,那么这些 header 中再次引入的其他 header 算不算呢?如果先改动了 header 再改动 cpp(这样 cpp 的时间戳是最新的),是否会触发编译呢?
    forcecharlie
        25
    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
        26
    secondwtq  
       2015-07-27 23:58:15 +08:00
    @forcecharlie 这倒是,Makefile 这种比较直接的底层工具不会去检测这种特定的东西。

    高级的工具貌似有专门去解决,我一般用 CMake,在 generate 的过程中会提取出 header 的依赖(据说是根据编译器提供的信息)。Xcode 也会自动触发编译,VS 大概不会没有。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3352 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 12:03 PVG 20:03 LAX 05:03 JFK 08:03
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86