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

2015-02-27 19:11:51 +08:00
 raincious
直接上代码:
https://gist.github.com/raincious/7b7fb2797b8941eebb39

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

现实:Stack overflow

VS告诉我出错的代码是在xmemory0或xtree里(多个地方),具体是这段:
https://gist.github.com/raincious/c8aebabcad95ccb8a2a0

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

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

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

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

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

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

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

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

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

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

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

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

好的好的,我就这么办。

竟然有两个大神来到了我的帖子里,真是受宠若惊。
1423
2015-02-27 21:57:08 +08:00
#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
2015-02-27 22:40:41 +08:00
@1423

感谢。但是不太理解呢。

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

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

难道要完全手动管理么?就是new出map的那些地址也要单独保存下什么的然后在最后一起释放?
yangff
2015-02-27 23:31:00 +08:00
@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
2015-02-27 23:47:46 +08:00
你这个问题的关键是使用了递归型的tree操作,tree的深度到了一定程度,怎么都会有call stack溢出的问题。即使你用一个pool来手动管理,要遍历或删除tree的一个分支时,只要还是递归型的tree操作,那还是老问题。解决方法是把tree操作写成非递归型的,请google non-recursive tree operation。
当然,对你的特定问题,如果不进行什么分支操作,能接受最后整颗树一起释放,那你把每次new出来的指针保存在一个list里最后统一delete就行了,即yangff的方法。
1423
2015-02-28 12:32:09 +08:00
。。。这不是析构的问题。。而是你那个 test 对象是在栈上的,他内部又有一个 map 而不是 map 的指针,你在循环中不断增大这个 map 的大小,他越来越大,就爆了,不过在 Mac 上想爆还真是得等好一会。
最后想说一个类的大小最好是常量,你这里完全可以用指针或引用
raincious
2015-02-28 13:08:32 +08:00
@yangff
@gamingcat1234
@1423

好的了解。感谢指导。

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

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

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

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

© 2021 V2EX