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

循环中向vector添加元素时触发segfault的原因排查

从map获取Chain指针后向vector添加Job时触发段错误

问题描述

编写的C++代码用于将标准输入解析为存储在vector<Chain>中的对象,程序在执行到chain->jobs.push_back(job);时触发段错误。排查发现,当第二次从map中获取chain指针时会出现该问题。

完整代码

#include <iostream>
#include <string>
#include <regex>
#include <vector>
#include <map>

using namespace std;

struct Job
{
    Job(){};
    Job(const Job& job) 
        : job_id_(job.job_id_), runtime_sec_(job.runtime_sec_), next_job_id_(job.next_job_id_) {};
    Job(int job_id, int runtime_sec, int next_job_id)
        : job_id_(job_id), runtime_sec_(runtime_sec), next_job_id_(next_job_id) {};
    Job& operator=(const Job& job){
        job_id_ = job.job_id_;
        runtime_sec_ = job.runtime_sec_;
        next_job_id_ = job.next_job_id_;
        return *this;
    }
    ~Job(){};
    int job_id_;
    int runtime_sec_;
    int next_job_id_;
};

struct Chain
{
    std::vector<Job> jobs;
};

void parse_input(std::vector<Chain>& chains)
{
    std::string line;
    std::getline(std::cin, line);
    std::map<int, Chain*> chain_map;
    
    Job job;
    std::regex job_regex("([0-9]+),([0-9]+),([0-9]+)");
    while (getline(std::cin, line))
    {
        std::cout << line << '\n' << std::flush;
        std::smatch matches;
        if (std::regex_match(line, matches, job_regex))
        {
            int job_id = std::stoi(matches[1].str());
            auto res = chain_map.find(job_id);
    
            Chain* chain;
            if (res == chain_map.end())
            {
                chains.emplace_back();
                chain = &chains.back();
            } else
            {
                chain = res->second;
            }
            Job job(std::stoi(matches[1].str()), std::stoi(matches[2].str()), std::stoi(matches[3].str()));
            chain->jobs.push_back(job);
            chain_map[job.next_job_id_] = chain;
        } else
        {
            std::cout << "Malformed input\n";
        }
        std::cout << chains.size() << '\n';
    }
}

int main()
{
    std::vector<Chain> chains;
    parse_input(chains);
    return 0;
}

测试用标准输入

job_id,runtime,next_job_id
1,30,23
2,23,3
3,20,0
23,50,4
4,43,0

栈追踪信息

Program received signal SIGSEGV, Segmentation fault.
0x000055555555e524 in std::construct_at<Job, Job const&> (__location=0x1e8e5f176c2a300c) at /usr/include/c++/11/bits/stl_construct.h:97
97          { return ::new((void*)__location) _Tp(std::forward<_Args>(__args)...); }
(gdb) bt
#0  0x000055555555e524 in std::construct_at<Job, Job const&> (
    __location=0x1e8e5f176c2a300c)
    at /usr/include/c++/11/bits/stl_construct.h:97
#1  0x000055555555e569 in std::allocator_traits<std::allocator<Job> >::construct<Job, Job const&> (__a=..., __p=0x1e8e5f176c2a300c)
    at /usr/include/c++/11/bits/alloc_traits.h:518
#2  0x000055555555cff8 in std::vector<Job, std::allocator<Job> >::push_back (
    this=0x5555555bd2a0, __x=...) at /usr/include/c++/11/bits/stl_vector.h:1192
#3  0x000055555555ad09 in parse_input (
    chains=std::vector of length 2, capacity 2 = {...}) at main.cpp:58
#4  0x000055555555aff8 in main () at main.cpp:85 

问题原因

核心问题在于std::vector的内存管理机制:当调用chains.emplace_back()时,如果vector当前容量不足以容纳新元素,会重新分配更大的内存块,将原有元素复制/移动到新内存后释放旧内存。这会导致之前存储在chain_map中的Chain*指针变成悬空指针——指向已被释放的内存区域。后续通过这些悬空指针访问Chain对象的jobs vector时,就会触发段错误。

以测试输入为例:

  1. 处理第一行1,30,23时,chains扩容(初始容量为0,添加第一个元素后容量变为1),chain_map[23]存储第一个Chain对象的地址;
  2. 处理第二行2,23,3时,chains再次扩容(容量从1变为2),原有第一个Chain对象被移动到新内存,而chain_map[23]仍保留旧地址;
  3. 处理第四行23,50,4时,从chain_map取出旧地址作为chain指针,调用chain->jobs.push_back()时访问失效内存,触发段错误。

解决方案

方案1:使用智能指针存储Chain对象

将vector<Chain>改为vector<unique_ptr<Chain>>,这样即使vector扩容,存储的是指针副本,Chain对象的实际内存地址不会改变,避免悬空指针问题。修改后的核心代码如下:

struct Chain
{
    std::vector<Job> jobs;
};

void parse_input(std::vector<std::unique_ptr<Chain>>& chains)
{
    std::string line;
    std::getline(std::cin, line);
    std::map<int, Chain*> chain_map;
    
    std::regex job_regex("([0-9]+),([0-9]+),([0-9]+)");
    while (getline(std::cin, line))
    {
        std::cout << line << '\n' << std::flush;
        std::smatch matches;
        if (std::regex_match(line, matches, job_regex))
        {
            int job_id = std::stoi(matches[1].str());
            auto res = chain_map.find(job_id);
    
            Chain* chain;
            if (res == chain_map.end())
            {
                chains.emplace_back(std::make_unique<Chain>());
                chain = chains.back().get();
            } else
            {
                chain = res->second;
            }
            Job job(std::stoi(matches[1].str()), std::stoi(matches[2].str()), std::stoi(matches[3].str()));
            chain->jobs.push_back(job);
            chain_map[job.next_job_id_] = chain;
        } else
        {
            std::cout << "Malformed input\n";
        }
        std::cout << chains.size() << '\n';
    }
}

int main()
{
    std::vector<std::unique_ptr<Chain>> chains;
    parse_input(chains);
    return 0;
}

方案2:提前预留足够容量

如果能预估chains需要存储的Chain对象数量,可调用chains.reserve(n)提前分配足够内存,避免后续扩容。比如测试输入最多需要3个Chain对象,可在parse_input开头添加chains.reserve(5);(预留余量),这样emplace_back不会触发扩容,指针始终有效。但该方法灵活性差,无法适配未知数量的输入。

方案3:调整逻辑,先构建映射再填充Job

先遍历所有输入,收集所有需要创建的Chain的ID,一次性创建所有Chain对象并建立映射,再再次遍历输入填充Job。该方法需额外存储输入行,逻辑稍复杂,但也能避免指针失效问题。

验证

使用方案1修改后的代码运行测试输入,不会再触发段错误,可正确将所有Job归类到对应的Chain中。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 09:59:55