You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++递归函数中使用vector引发栈溢出问题求解

问题描述

我正在开发一个用于加载Collada文件的XML解析器,目前定义了一个递归函数loadNode来加载每个XML节点,代码如下:

XMLnode* XMLparser::loadNode(std::vector<std::string> lines, unsigned int level) {  

    assert(level < lines.size());
    std::string line = trim(lines[level]);

    // if the line starts with a closing tag (</...) it means that is the closing tag of a
    // previous node, therefore, don't create another node
    if (line.rfind("</", 0) == 0) {

        // before returning, load child nodes.
        // recursion condition: process the following file's line, only if it does exist
        // (which means that the level must be < of the total number of line of the file)
        if (level < lines.size() - 1) {
            level++; XMLnode* node = loadNode(lines, level);
        }

        return nullptr;

    }


    std::string startingTag = g_startTag(line);
    std::vector<std::string> startingTagParts;


    // recursion condition: process the following file's line, only if it does exist
    // (which means that the level must be < of the total number of line of the file)
    if (level < lines.size() - 1) {
        level++; XMLnode* node = loadNode(lines, level);
    }
    
    return nullptr;

}

其中XMLnode是自定义类(所有函数包括loadNode均为static),trim是用于去除字符串左右空格的自定义函数。loadNode接收存储XML文件每行内容的std::vector<std::string>,g_startTag函数会返回当前处理行的起始标签内容(例如对于行<source id="Cube-mesh-colors-Col" name="Col">,会返回去除<和>后的标签内容)。

问题在于,每当我尝试在loadNode中声明std::vector<std::string>来存储startingTag拆分操作的结果时,处理一定行数后程序会崩溃,抛出如下栈溢出异常:

Exception not handled at 0x00007FF6CB96597F in "project_name": 0xC00000FD: Stack overflow (parameters: 0x0000000000000001, 0x0000001D92803FB8).

我推测这是递归持续声明新vector导致的。此外,当我调用自定义的split函数时,也会触发类似的栈溢出异常:

Exception not handled at 0x00007FFCC8408739 (ucrtbased.dll) in "project_name": 0xC00000FD: Stack overflow (parametri: 0x0000000000000001, 0x000000C84C2B3FE8).

我的split函数定义如下:

std::vector<std::string> split(std::string str, const char delimiter, bool removePunctuation= false) {

    std::vector<std::string> result = {};
    
    // using string streams to split the string
    std::istringstream stream(str);
    std::string word;
    while (std::getline(stream, word, delimiter)) {

        // add the new word to the vector of words
        if (removePunctuation)
            word.erase(std::remove_if(word.begin(), word.end(), ispunct), word.end());
        result.push_back(word);

    }

    return result;

}

我曾用打印语句调试loadNode函数:

std::cout << "level: " << level << "; starting tag: " << startingTag << std::endl;

加入split相关语句后,能处理的行数明显减少;即使不直接在loadNode中声明vector,而是调用split函数(其内部会创建临时vector),程序仍会在处理若干行后崩溃。请问有什么方法可以解决这个问题?

解决方案

1. 修复递归逻辑的核心错误

你的loadNode递归逻辑完全不符合XML嵌套结构的处理规则:遇到闭合标签时仍递归调用loadNode,导致递归深度直接等于文件总行数,这才是栈溢出的根本原因,和vector本身关系不大。正确的XML递归处理逻辑应该是:

  • 遇到开始标签:创建当前节点,然后递归处理其子节点,直到遇到对应的闭合标签
  • 遇到闭合标签:返回nullptr,通知上层递归当前节点的子节点处理完毕

修正后的递归逻辑示例:

XMLnode* XMLparser::loadNode(const std::vector<std::string>& lines, unsigned int& level) {  
    assert(level < lines.size());
    std::string line = trim(lines[level]);

    // 处理闭合标签:跳过当前行,返回nullptr结束当前层级
    if (line.rfind("</", 0) == 0) {
        level++;
        return nullptr;
    }

    // 创建当前节点
    std::string startingTag = g_startTag(line);
    XMLnode* currentNode = new XMLnode(startingTag); // 假设XMLnode有对应构造函数

    // 安全调用split拆分标签属性
    std::vector<std::string> tagParts = split(startingTag, ' ');
    // 此处添加属性解析逻辑...

    level++; // 移动到下一行,处理子节点
    while (level < lines.size()) {
        XMLnode* child = loadNode(lines, level);
        if (child == nullptr) {
            // 遇到闭合标签,子节点处理完成
            break;
        }
        currentNode->addChild(child); // 假设XMLnode有添加子节点的方法
    }

    return currentNode;
}

注意:将level改为引用传递,确保所有递归层级共享同一个行号指针,避免重复处理或遗漏行。

2. 减少栈内存占用

  • 容器传递用引用:原函数中std::vector<std::string> lines是值传递,每次递归都会复制整个vector,极大消耗栈空间。改为const std::vector<std::string>& lines,避免不必要的复制。
  • 优化临时变量:split返回的vector存储区在堆上,但递归过深时,栈上的临时字符串、流对象叠加也会占用大量空间。优先修复递归逻辑,若仍有问题,可将部分临时变量改为静态或堆分配。

3. 替换递归为迭代实现(应对极深嵌套)

如果XML文件层级极深(比如上万层嵌套),即使修复递归逻辑,仍可能触发栈溢出。此时用迭代方式模拟递归,完全在堆上操作:

XMLnode* XMLparser::loadNodeIterative(const std::vector<std::string>& lines) {
    if (lines.empty()) return nullptr;

    std::stack<std::pair<XMLnode*, unsigned int>> nodeStack;
    XMLnode* root = nullptr;
    unsigned int level = 0;

    while (level < lines.size()) {
        std::string line = trim(lines[level]);

        if (line.rfind("</", 0) == 0) {
            // 闭合标签:弹出栈顶节点
            if (!nodeStack.empty()) nodeStack.pop();
            level++;
            continue;
        }

        // 创建当前节点并处理属性
        std::string startingTag = g_startTag(line);
        XMLnode* currentNode = new XMLnode(startingTag);
        std::vector<std::string> tagParts = split(startingTag, ' ');
        // 属性解析逻辑...

        // 将节点加入父节点的子列表
        if (nodeStack.empty()) {
            root = currentNode;
        } else {
            nodeStack.top().first->addChild(currentNode);
        }

        // 压入栈,准备处理子节点
        nodeStack.push({currentNode, level});
        level++;
    }

    return root;
}

4. 优化split函数

  • 字符串参数用const std::string&,避免值传递复制;
  • 提前预留vector空间,减少内存分配次数(性能优化);
  • 过滤拆分后的空字符串,避免无效元素。

优化后的split函数:

std::vector<std::string> split(const std::string& str, const char delimiter, bool removePunctuation = false) {
    std::vector<std::string> result;
    result.reserve(8); // 预估标签属性数量,提前预留空间

    std::istringstream stream(str);
    std::string word;
    while (std::getline(stream, word, delimiter)) {
        if (removePunctuation) {
            word.erase(std::remove_if(word.begin(), word.end(), ispunct), word.end());
        }
        if (!word.empty()) {
            result.push_back(word);
        }
    }

    return result;
}

内容的提问来源于stack exchange,提问作者Luke__

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 08:48:21