【问题标题】:Stack overflow when debugging but not in release调试时堆栈溢出,但在发布时不溢出
【发布时间】:2011-08-05 23:13:38
【问题描述】:

我有下面的代码解析一个文本文件并索引单词和行:

bool Database::addFromFileToListAndIndex(string path, BSTIndex* & index, list<Line *> & myList)
{
    bool result = false;
    ifstream txtFile;
    txtFile.open(path, ifstream::in);
    char line[200];
    Line * ln;
    //if path is valid AND is not already in the list then add it
    if(txtFile.is_open() && (find(textFilePaths.begin(), textFilePaths.end(), path) == textFilePaths.end())) //the path is valid
    {
        //Add the path to the list of file paths
        textFilePaths.push_back(path);
        int lineNumber = 1;
        while(!txtFile.eof())
        {
            txtFile.getline(line, 200);
            ln = new Line(line, path, lineNumber);
            if(ln->getLine() != "")
            {
                lineNumber++;
                myList.push_back(ln);
                vector<string> words = lineParser(ln);
                for(unsigned int i = 0; i < words.size(); i++)
                {
                    index->addWord(words[i], ln);
                }
            }
        }
        result = true;
    }
    return result;
}

在我给它一个巨大的文本文件之前,我的代码可以完美且相当快速地运行。然后我从 Visual Studio 收到堆栈溢出错误。当我切换到“发布”配置时,代码运行顺利。我的代码有问题还是在运行“调试”配置时存在某种限制?我是否试图在一个功能中做太多事情?如果是这样,我该如何分解它以使其在调试时不会崩溃?

编辑 根据请求,我的 addWord 实现;

void BSTIndex::addWord(BSTIndexNode *& pCurrentRoot, string word, Line * pLine)
    {
        if(pCurrentRoot == NULL)  //BST is empty
        {
            BSTIndexNode * nodeToAdd = new BSTIndexNode();
            nodeToAdd->word = word;
            nodeToAdd->pData = pLine;
            pCurrentRoot = nodeToAdd;
            return;
        }
        //BST not empty
        if (word < (pCurrentRoot->word)) //Go left
        {
            addWord(pCurrentRoot->pLeft, word, pLine);
        }
        else //Go right
        {
            addWord(pCurrentRoot->pRight, word, pLine);
        }
    }

还有lineParser:

vector<string> Database::lineParser(Line * ln) //Parses a line and returns a vector of the words it contains
{
    vector<string> result;
    string word;
    string line = ln->getLine();
    //Regular Expression, matches anything that is not a letter, number, whitespace, or apostrophe
    tr1::regex regEx("[^A-Za-z0-9\\s\\']");
    //Using regEx above, replaces all non matching characters with nothing, essentially removing them.
    line = tr1::regex_replace(line, regEx, std::string(""));

    istringstream iss(line);
    while(iss >> word)
    {
        word = getLowercaseWord(word);
        result.push_back(word);
    }
    return result;
}

【问题讨论】:

  • 这与问题无关,但似乎有空行泄漏。该代码创建了一个new Line(...) 对象,如果该行为空,则似乎永远不会删除它。
  • @Mark 谢谢,你是对的,现在已经修复了。
  • @sarnold 不,这不是堆栈溢出的原因。他只是指出这一点,这就是他评论而不是回答的原因。
  • @Pete:你的巨大文本文件有多大?如果您只是在 Release 中可用地址空间的限制范围内,因为 Debug 可能会在内存中添加一些东西(堆栈保护,...),您可能会超出限制然后崩溃。
  • 您也可能正在破坏您的堆栈,并且只能在调试中检测到它,这要归功于堆栈防护(不确定 VS 是如何处理的)。如果你能用 valgrind 之类的东西来检查它会很酷。

标签: c++ stack-overflow


【解决方案1】:

堆栈溢出表明您的堆栈空间已用完(可能很明显,但以防万一)。典型的原因是非终止或过度递归,或非常大的堆栈对象重复。有趣的是,在这种情况下也可能是这样。

很可能在 Release 中,您的编译器正在执行尾调用优化,以抑制过度递归导致堆栈溢出。

也可能在 Release 中,您的编译器正在优化 lineParser 的向量返回副本。

所以你需要找出 Debug 中哪个条件溢出,我会从递归作为最可能的罪魁祸首开始,尝试将字符串参数类型更改为引用,即。

void BSTIndex::addWord(BSTIndexNode *& pCurrentRoot, string & word, Line * pLine)

这应该会阻止您在每次嵌套调用 addWord 时复制 word 对象。

还可以考虑添加一个 std::cout

【讨论】:

  • 是的,绝对是尾声。
  • 我认为递归调用肯定是问题所在。我接受了您的建议,并在 addWord 中放置了一个静态计数器。它在每次递归调用之前递增,然后在函数结束时打印出来并重置。该文件仍在解析中,但到目前为止,当添加一些单词时,我最多有 1500 多个递归调用,并且随着 BST 大小的增加,这个数字只会变得更大。当我在“调试”配置中时,我是否应该担心堆栈溢出,因为它在“发布”配置中工作正常?
  • 3,469 次调用深度,我得到了堆栈溢出。
  • @Pete 我认为您应该尽量避免在 Debug 和 Release 中出现堆栈溢出,因为这会使调试其他问题变得复杂。 Chris Dodd 为实现提供了更好的替代方案,除非您绝对必须使用递归。同时,我还建议您再看看您的数据结构以及您希望它实现的目标 - 也许哈希图或 Trie 可以更好地工作?而且看起来您正在跨多个节点复制单词条目(这可能是指向单词的共享指针)。
  • 自从我接受了这个答案后,我想说我最终实际上改变了数据结构并保留了递归调用(导致堆栈溢出)。我认为我的问题的根源是我存储了一个单词的每个实例。数据结构现在只存储唯一的单词,每个单词都有一个指向一组行的指针。除了消除过多的递归之外,这实际上改进了我的代码。
【解决方案2】:

问题几乎可以肯定是 addWord 中的递归调用——在非优化构建中,这将消耗大量堆栈空间,而在优化构建中,编译器会将其转换为尾调用,它重用相同的堆栈框架。

您可以很容易地手动将递归调用转换为循环:

void BSTIndex::addWord(BSTIndexNode ** pCurrentRoot, string word, Line * pLine)
{
    while (*pCurrentRoot != NULL) {
        //BST not empty
        if (word < (*pCurrentRoot)->word) //Go left
        {
            pCurrentRoot = &(*pCurrentRoot)->pLeft;
        }
        else //Go right
        {
            pCurrentRoot = &(*pCurrentRoot)->pRight;

        }
    }
    //BST is empty
    BSTIndexNode * nodeToAdd = new BSTIndexNode();
    nodeToAdd->word = word;
    nodeToAdd->pData = pLine;
    *pCurrentRoot = nodeToAdd;
}

【讨论】:

  • 建议是合理的,缺乏实施。我知道也缺少原始实现,但这不是在提议的解决方案中在必要时不使用正确智能指针的原因。因为 OP(或他之后的任何问题)的下一个问题将是:Eh!我实现了 Chris 的解决方案,但我遇到了内存泄漏,请帮助!;我理解简短示例的意愿,但不应以简洁简单的名义放弃良好的编码实践,因为从长远来看这是一种损失。
【解决方案3】:

您也应该发布您的堆栈,这实际上会显示导致溢出的原因。看起来很明显 addWord 中的递归显着消耗堆栈内存。

如果您只想让它工作,请进入您的编译器/链接器设置并增加为您的堆栈保留的大小。默认情况下它只有 1MB,将其提高到 32MB 或其他大小,无论调试版本有多少额外的计数器或探针,您都可以放心,您将有足够的堆栈来处理它。

【讨论】:

  • 是的,堆栈中的最后一件事是对 addWord 的所有调用(很多调用)。我正在做一些测试,堆栈中的 addWord 调用量随着我添加更多单词而增加。如何在保持函数递归的同时缓解这种情况?
【解决方案4】:

您可以将堆栈的大小增加到适当的字节数。

#pragma comment(linker, "/STACK:1000000000")  

【讨论】:

    猜你喜欢
    • 2010-10-20
    • 2019-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-30
    • 1970-01-01
    • 2021-05-28
    • 1970-01-01
    相关资源
    最近更新 更多