V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
raincious
V2EX  ›  C

就这么 Stack overflow 了,一定是有什么我不知道的地方。求赐教。

  •  
  •   raincious · 2015-02-27 19:11:51 +08:00 · 2037 次点击
    这是一个创建于 3555 天前的主题,其中的信息可能已经有所发展或是发生改变。
    直接上代码:


    猜想是:
    当我通过ptest访问SubItems时,map会自动帮我初始化好对应的ContainerStructEnitiy实例,这样我就能继续拿新的ContainerStructEnitiy的地址继续往里爬,直到一个树杈完全建好。

    现实:Stack overflow

    VS告诉我出错的代码是在xmemory0或xtree里(多个地方),具体是这段:


    看起来像是释放的时候出现的问题。

    我测试了下,将loop的数值改小(比如改到10、128、256)就不会出现这种情况。最后发现的阀值是338,如果loop超过338就会遇到Stack overflow,小于则不会。不知道会不会是因为我遇到了某种限制。

    为什么会出现这样的错误呢?请赐教。

    感谢。
    20 条回复    2015-02-28 13:08:32 +08:00
    gamingcat1234
        1
    gamingcat1234  
       2015-02-27 20:00:49 +08:00
    不是说了是stack overflow吗?
    http://en.wikipedia.org/wiki/Stack_overflow
    你这个奇妙的嵌套数据结构里所有东西都分配在stack上。
    raincious
        2
    raincious  
    OP
       2015-02-27 20:14:40 +08:00
    @gamingcat1234

    感谢,貌似这个数据结构确实不太好,但是不知道怎么实现类似的,于是就糟弄了一把。

    但,我想知道的是如何防止这个Stack overflow的问题。关键是在我不知道为什么会有那个Magic number 338在。
    gamingcat1234
        3
    gamingcat1234  
       2015-02-27 20:42:34 +08:00   ❤️ 1
    map <wstring, ContainerStructEnitiy*> SubItems;
    然后用new和delete自己分配内存
    sumhat
        4
    sumhat  
       2015-02-27 20:48:17 +08:00   ❤️ 1
    取决于你的 stack 大小和结构体的大小,你可以在 VS 里调大 stack size,然后数值就不是 338 了。
    raincious
        5
    raincious  
    OP
       2015-02-27 21:02:51 +08:00
    @gamingcat1234
    其实我不想手动管理那些内存哈。不过分配在堆上我没试过,或许能解决这个问题?

    @sumhat
    目前看来这就是解决方案了。但貌似真正的问题恐怕是这个数据结构有问题哈。
    zcbenz
        6
    zcbenz  
       2015-02-27 21:13:49 +08:00
    @raincious 你的头像好烦……
    yangff
        7
    yangff  
       2015-02-27 21:15:32 +08:00
    linux gcc 4.9.2 20150204 复现不能.
    raincious
        8
    raincious  
    OP
       2015-02-27 21:19:18 +08:00
    @zcbenz
    呵呵,其实……这样才有特点……

    @yangff
    或许是GCC的栈尺寸比较大?我在VS2013Express下面写的。
    yangff
        9
    yangff  
       2015-02-27 21:22:24 +08:00   ❤️ 1
    是这样的,你分配在哪里都没有用.
    你前面的代码都没有问题, 思路也没有问题.
    但是结束的时候, stl会尝试析构ContainerStructEnitiy.
    这肯定是要的..那么问题来了, stl会递归的析构ContainerStructEnitiy下面的各种对象, 比如那个map, map析构的时候又会递归的析构更下面的ContainerStructEnitiy,如此往复, 最后这个析构就把栈炸了.
    yangff
        10
    yangff  
       2015-02-27 21:25:14 +08:00   ❤️ 1
    唯一的办法就是...用指针. 然后手动维护对象生命周期.
    不难发现,就算用智能指针也都会有析构的时候爆栈的问题...所以只能手动维护了..
    raincious
        11
    raincious  
    OP
       2015-02-27 21:30:03 +08:00
    @yangff

    嗯,这解释了为什么会在释放的时候出问题。

    事实上之前我也尝试过用ContainerStructEnitiy*以及new和delete构建相同的功能(只是ContainerStructEnitiy变成了Class,有自己的析构来释放自己SubItems里的对象),然后发现只要我不叫delete,就不会出现问题(虽然这样真的有问题)。

    太悲剧了,请问有办法解决这个问题么?还是我只能放弃这个结构了?
    yangff
        12
    yangff  
       2015-02-27 21:32:36 +08:00   ❤️ 1
    @raincious
    ContainerStructEnitiy 改成ContainerStructEnitiy*, 然后把所有的ContainerStructEnitiy*放在pool里面存起来手动delete 一.个.一.个.的delete.
    Cee
        13
    Cee  
       2015-02-27 21:41:07 +08:00   ❤️ 1
    yangff 說的對,用指針維護即可。

    還有不是「阀(fa)值」,是「阈(yu)值」。
    raincious
        14
    raincious  
    OP
       2015-02-27 21:43:56 +08:00
    @yangff
    @Cee

    好的好的,我就这么办。

    竟然有两个大神来到了我的帖子里,真是受宠若惊。
    1423
        15
    1423  
       2015-02-27 21:57:08 +08:00   ❤️ 1
    #include <iostream>

    #include <map>

    using namespace std;

    struct ContainerStructEnitiy
    {
    wstring Name;
    map <wstring, ContainerStructEnitiy> * pSubItems;
    };

    int main()
    {
    int loop = 102000;

    ContainerStructEnitiy test;
    ContainerStructEnitiy *ptest = &test;

    while (loop-- >= 0)
    {
    ptest->pSubItems = new map <wstring, ContainerStructEnitiy>;
    (*(ptest->pSubItems))[to_wstring(loop)].Name = L"blablabla";
    ptest = &(*(ptest->pSubItems))[to_wstring(loop)];
    cout << loop;
    }

    return 0;
    }

    这样行 C++ 好久没用了,凑活看
    刚才还没上面的回复。。看来人还是很多的
    raincious
        16
    raincious  
    OP
       2015-02-27 22:40:41 +08:00
    @1423

    感谢。但是不太理解呢。

    是说在时候手动清理内存么?因为我看到了new map,测试之后发现函数运行完成返回后内存并没有释放,估计肯定还在堆里。

    然后为了偷懒我把代码改成了这样,结果Stack overflow(嗯,就是又造成了我之前的问题):
    https://gist.github.com/raincious/0f8b527de148e503bd03
    可能我没理解对。

    难道要完全手动管理么?就是new出map的那些地址也要单独保存下什么的然后在最后一起释放?
    yangff
        17
    yangff  
       2015-02-27 23:31:00 +08:00   ❤️ 1
    @raincious
    就是说
    class ContainerStructEnitiy
    {
    public:
    wstring Name;
    map <wstring, ContainerStructEnitiy[*]<<<<---这里要用指针> (*)<<<<<<---这里不用指针没问题pSubItems;

    ContainerStructEnitiy();
    ~ContainerStructEnitiy();
    };

    这里的问题在于, 析构ContainerStructEnitiy的时候会析构里面的map, 而map会析构他包含的所有元素,也就是那一堆ContainerStructEnitiy, 这个过程是递归进行的, 因此会造成栈溢出. 不管你是用delete还是c++自己析构, 都会导致这个问题.

    解决的办法在于把map对ContainerStructEnitiy的直接管理改成对ContainerStructEnitiy*也就是指针的管理,这样c++在析构map,只会释放保存ContainerStructEnitiy*的内存, 而不会进一步析构他所包含的ContainerStructEnitiy, 然后, 你手动记下所有的ContainerStructEnitiy*, 然后自己把他们delete掉, 就没有问题了.
    gamingcat1234
        18
    gamingcat1234  
       2015-02-27 23:47:46 +08:00   ❤️ 1
    你这个问题的关键是使用了递归型的tree操作,tree的深度到了一定程度,怎么都会有call stack溢出的问题。即使你用一个pool来手动管理,要遍历或删除tree的一个分支时,只要还是递归型的tree操作,那还是老问题。解决方法是把tree操作写成非递归型的,请google non-recursive tree operation。
    当然,对你的特定问题,如果不进行什么分支操作,能接受最后整颗树一起释放,那你把每次new出来的指针保存在一个list里最后统一delete就行了,即yangff的方法。
    1423
        19
    1423  
       2015-02-28 12:32:09 +08:00 via Android   ❤️ 1
    。。。这不是析构的问题。。而是你那个 test 对象是在栈上的,他内部又有一个 map 而不是 map 的指针,你在循环中不断增大这个 map 的大小,他越来越大,就爆了,不过在 Mac 上想爆还真是得等好一会。
    最后想说一个类的大小最好是常量,你这里完全可以用指针或引用
    raincious
        20
    raincious  
    OP
       2015-02-28 13:08:32 +08:00
    @yangff
    @gamingcat1234
    @1423

    好的了解。感谢指导。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3326 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 04:56 · PVG 12:56 · LAX 20:56 · JFK 23:56
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.