std::vector和调整大小时的内存错误

3

我有一个定义如下的结构:

struct Edge
{
    int u, v;   // vertices

    Edge() { }
    Edge(int u, int v)
    {
        this->u = u;
        this->v = v;
    }
};

并且定义了一个类字段,如下所示

vector<Edge> solution;

在我创建新的 Edge 并像这样将它们推入向量中的一个方法中(这是我真实代码的极度简化,但问题依然存在):
solution.push_back(Edge(1, 2));
solution.push_back(Edge(3, 4));
solution.push_back(Edge(5, 6));
solution.push_back(Edge(7, 8));
solution.push_back(Edge(9, 10));
solution.push_back(Edge(11, 12));
solution.push_back(Edge(13, 14)); // adding 7th element; the problem occurs here

当执行最后一个push_back时,在Visual Studio的调试模式下出现了一个错误窗口,调试器跳转到malloc.c文件的_heap_alloc函数结尾处,并弹出一个断点。在这个第七行之前,向量看起来可以正常工作。我可以在调试器中看到所有的元素。似乎这个向量在重新分配自己(扩展大小)时存在问题。
有趣的是,如果我将以下内容放在所有推回操作之前:
solution.reserve(7);

第七个边已经被正确添加。更有趣的是,尝试为超过22个元素保留空间也会导致上述错误。

我做错了什么?我该如何调试?应用程序的其余部分并没有使用太多内存,所以我不相信堆已经满了。


需要更多代码,请请求。这是一个比较粗糙的Metric Travelling Salesman's Problem 2-近似算法实现。它首先创建最小生成树,然后按DFS顺序将顶点(只是索引)添加到partialSolution向量中。

void ApproxTSPSolver::Solve()
{
    // creating a incidence matrix
    SquareMatrix<float> graph(noOfPoints);

    for (int r=0; r<noOfPoints; r++)
    {
        for (int c=0; c<noOfPoints; c++)
        {
            if (r == c)
                graph.SetValue(r, c, MAX);
            else
                graph.SetValue(r, c, points[r].distance(points[c]));
        }
    }

    // finding a minimum spanning tree
    spanningTree = SquareMatrix<bool>(noOfPoints);

    // zeroeing the matrix
    for (int r=0; r<noOfPoints; r++)
        for (int c=0; c<noOfPoints; c++)
            spanningTree.SetValue(r, c, false);

    bool* selected = new bool[noOfPoints];
    memset(selected, 0, noOfPoints*sizeof(bool));
    selected[0] = true; // the first point is initially selected

    float min;
    int minR, minC;

    for (int i=0; i<noOfPoints - 1; i++)
    {
        min = MAX;

        for (int r=0; r<noOfPoints; r++)
        {
            if (selected[r] == false)
                continue;

            for (int c=0; c<noOfPoints; c++)
            {
                if (selected[c] == false && graph.GetValue(r, c) < min)
                {
                    min = graph.GetValue(r, c);
                    minR = r;
                    minC = c;
                }
            }
        }

        selected[minC] = true;
        spanningTree.SetValue(minR, minC, true);
    }

    delete[] selected;

    // traversing the tree
    DFS(0);

    minSol = 0.0f;

    // rewriting the solution to the solver's solution field
    for (int i=0; i<noOfPoints - 1; i++)
    {
        solution.push_back(Edge(partialSolution[i], partialSolution[i + 1]));
        minSol += points[partialSolution[i]].distance(points[partialSolution[i + 1]]);
    }

    solution.push_back(Edge(partialSolution[noOfPoints - 1], partialSolution[0]));
    minSol += points[partialSolution[noOfPoints - 1]].distance(points[partialSolution[0]]);

    cout << endl << minSol << endl;

    solved = true;
}

void ApproxTSPSolver::DFS(int vertex)
{
    bool isPresent = std::find(partialSolution.begin(), partialSolution.end(), vertex)
        != partialSolution.end();

    if (isPresent == false)
        partialSolution.push_back(vertex); // if I comment out this line, the error doesn't occur

    for (int i=0; i<spanningTree.GetSize(); i++)
    {
        if (spanningTree.GetValue(vertex, i) == true)
            DFS(i);
    }
}


class ApproxTSPSolver : public TSPSolver
{
    vector<int> partialSolution;
    SquareMatrix<bool> spanningTree;
    void DFS(int vertex);

public:
    void Solve() override;
};

来自main.cpp

TSPSolver* solver;
    string inputFilePath, outputFilePath;

    // parsing arguments
    if (ArgParser::CmdOptionExists(argv, argv + argc, "/a"))
    {
        solver = new ApproxTSPSolver();
    }
    else if (ArgParser::CmdOptionExists(argv, argv + argc, "/b"))
    {
        solver = new BruteForceTSPSolver();
    }
    else
    {
        solver = new BranchAndBoundTSPSolver();
    }

    inputFilePath = ArgParser::GetCmdOption(argv, argv + argc, "/i");
    outputFilePath = ArgParser::GetCmdOption(argv, argv + argc, "/s");

    solver->LoadFromFile(inputFilePath);

    Timer timer;
    timer.start();
    solver->Solve();
    timer.stop();

    cout << timer.getElapsedTime();

TSPSolver.c的一部分:

TSPSolver::TSPSolver()
{
    points = NULL;
    solved = false;
}

TSPSolver::~TSPSolver()
{
    if (points)
        delete[] points;
}

void TSPSolver::LoadFromFile(string path)
{
    ifstream input(path);
    string line;
    int nodeID;
    float coordX, coordY;
    bool coords = false;

    minX = numeric_limits<float>::max();
    maxX = numeric_limits<float>::min();
    minY = numeric_limits<float>::max();
    maxY = numeric_limits<float>::min();

    while (input.good())
    {
        if (coords == false)
        {
            getline(input, line);

            if (line == "NODE_COORD_SECTION")
            {
                coords = true;
            }
            else if (line.find("DIMENSION") != string::npos)
            {
                int colonPos = line.find_last_of(":");
                noOfPoints = stoi(line.substr(colonPos + 1));
#ifdef _DEBUG
                cout << noOfPoints << " points" << endl;
#endif

                // allocating memory for this amount of points
                points = new Point[noOfPoints];
            }
        }
        else
        {
            input >> nodeID >> coordX >> coordY;

            points[nodeID - 1].X = coordX;
            points[nodeID - 1].Y = coordY;

            minX = min(minX, coordX);
            maxX = max(maxX, coordX);
            minY = min(minY, coordY);
            maxY = max(maxY, coordY);

            if (nodeID == noOfPoints)
            {
                break;
            }
        }
    }

    input.close();
}

7
你的代码看起来是正确的。也许你的程序在其他地方有错误,例如向无效的内存地址写入。 - Danvil
@Cyber,在实际代码中,向量最初为空。因此,在尝试向向量添加第七个元素时发生错误。 - PiotrK
2
@PiotrK:你可以尝试这个:http://valgrind.org/docs/manual/mc-manual.html - Danvil
你能看到回溯(即调用函数堆栈)吗?它应该准确地显示出你代码中哪一行引入了这个问题。 - Michał Góral
2
请使用Edge(int uu, int vv):u(uu),v(vv){}构造函数,它比您当前的构造函数更有效率。 - TemplateRex
显示剩余14条评论
2个回答

0

这更像是一条评论而不是答案,但空间太有限了。

如果您使用的是Windows,请尝试Microsoft Application Verifier。它可能会检测到错误的内存访问。

另一种检测此类访问的方法是保留初始化为0的空字符数组。

打开声明向量的类并在其前后声明一个64个字符的char数组,并将它们初始化为0!然后进入生成错误的向量代码并检查这些填充数组的内容。如果它们被填充了,那么就有人写入了比应该更多的内容。

定位“恶意”访问的一种方法(至少在VC++中)是设置数据断点,写入您的填充数组并检查调用堆栈。


感谢提供的想法。'zeros' 方法没有返回任何有趣的东西,两个数组仍然填充着0。我不能设置数据断点来“扫描”超过4个字节的位置,这使它们基本上无用。我得到了这个错误提示:"无法设置断点。硬件不支持监视所请求的字节数。" 我必须尝试你们提供的工具,也许其中一些会告诉我些什么。 - PiotrK

0

您可能在各个地方进行points的越界访问,例如这个:

input >> nodeID >> coordX >> coordY;
points[nodeID - 1].X = coordX;

如果输入失败或值超出范围怎么办?
我建议从代码中删除所有使用newdelete以及[]的地方;例如,假设pointsint *points;,则将其替换为std::vector<int> points。将所有[]访问更改为.at()并捕获异常。在所有没有正确复制语义的类上禁用复制。
然后,您可以更加确定它不是内存分配错误、复制错误或越界访问(这些都是解释您的症状的强有力的候选者)。
这也会解决TSPSolver当前没有正确复制语义的问题。
制作一个SSCCE将非常有用。您提到有“很多输入”,请尝试将输入减少到最小,但仍然出现问题。SSCCE可以包括输入数据,只要它是您可以发布的可管理大小。目前,您展示了太多的代码,但还不够,正如他们所说的那样。问题仍然潜伏在您尚未发布的某个地方。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接