【问题标题】:Deleting a pointer a different places results in different behaviors (crash or not)将指针删除到不同的地方会导致不同的行为(崩溃与否)
【发布时间】:2010-11-20 21:47:19
【问题描述】:

此问题是对this one 的改进,其方向与预期不同。

在我的多线程应用程序中,主线程创建参数并存储它们:

typedef struct {
    int parameter1;
    double parameter2;
    float* parameter3;
} jobParams;

typedef struct {
    int ID;
    void* params;
} jobData;

std::vector<jobData> jobs;

// main thread
for (int i = 0; i < nbJobs; ++i) {
    jobParams* p = new jobParams;
    // fill and store params

    jobData data;
    data.ID = i;
    data.params = p;

    jobs.push_back(data);
}

// start threads and wait for their execution

// delete parameters
for (int i = 0; i < jobs.size(); ++i) {
    delete jobs[i].params;
}

然后,每个线程获取一个指向一组参数的指针,并用它调用一个作业函数:

// thread (generic for any job function and any type of params)
jobData* job = main->getNextParams();
jobFunction(job->ID, job->params);

整个过程将 void* 作为参数,以便能够对参数使用任何结构,但随后作业函数将其转换回正确的结构:

void* jobFunction(void* param) {
    jobParams* params = (jobParams*) param;
    // do stuff
    return 0;
}

我的问题如下:如果我在jobFunction() 的末尾加上delete params,它可以完美运行。但是,我更愿意让线程或主线程负责删除,这样我就不必记住删除我编写的每个 jobFunction() 的参数。

如果我在线程中调用jobFunction() 之后尝试delete params,甚至在确保所有线程都已完成(因此不再需要参数)之后在主线程中尝试,我得到堆损坏错误:

HEAP[prog]: 指定给 RtlFreeHeap(02E90000, 03C2EE38) 的地址无效

我使用的是 Visual Studio 2008 Pro,因此无法使用 valgrind 或其他 *nix 工具进行调试。 “子线程”对主线程的所有访问都是使用互斥锁同步的,所以问题不在于我两次删除相同的参数。

实际上,通过使用 VS 内存查看器,我知道 jobParams 指针指向的内存在 jobFunction() 结束和我尝试删除它的点之间(无论是在主线程中还是在“子线程”)。

我添加了两个结构的定义,以及我想要删除参数的方式。

【问题讨论】:

  • 我们可以看看你如何删除作业->参数吗?你不应该有任何问题做你的建议(我自己从来没有遇到过类似的问题)。我们还可以看到 jobData 类/结构定义吗?
  • 我都添加了。如果我只是删除 jobFunction() 中的参数,它可以工作,但我想在主线程中执行它,如图所示。
  • 棘手,一切看起来都不错。既然您说如果您尝试从主线程中删除就会出现问题,那么连接代码中可能存在一些错误。是否可以在问题文本中显示其中一些代码?
  • 加入代码是什么意思?我等待线程完成的代码?
  • 是的,我就是这个意思。

标签: c++ multithreading pointers memory-management heap-memory


【解决方案1】:

只是一个想法..你可以试试

for (int i = 0; i < jobs.size(); ++i) {
    delete (jobParams*)jobs[i].params;
}

新建类型 jobParams 然后删除 void* 可能是您的问题的原因。

是否有任何理由将参数作为 void* 存储在 jobData 中?我认为如果您希望拥有不同类型的 jobParams,那么您应该使用继承层次结构,而不是盲目地转换为 void*。

【讨论】:

  • 是的,我使用 void* 的原因是能够拥有不同种类的参数。我可以使用继承,但我发现 void* 解决方案不太复杂。
  • 在删除之前进行强制转换可以解决您的问题吗?因为无论你怎么看,它都不是“正确”的解决方案。
  • 刚刚在VS2008下检查过,简化的尝试不会抛出你所说的错误......
  • 如果我删除该项目两次,它会这样做。我认为我们需要查看更多您的代码,因为您肯定会多次释放。
  • 接受了这个答案,因为您是参与度最高的。我最后做了不同的事情,我找不到这种行为的原因!还是谢谢!
【解决方案2】:

这种错误通常意味着您在某处存在数据竞争。 main->getNextParams() 是否会做正确的事情,即使它同时被多个线程调用?如果它为两者提供相同的参数,则您可以拥有一个双释放。

还有,而不是

jobFunction(jobData->ID, jobData->params);

你可能是说

jobFunction(job->ID, job->params);

【讨论】:

  • 是的,getNextParams() 是线程安全的。我使用互斥锁来确保不会两次返回相同的参数。无论如何,如果是这样的问题,删除 jobFunction() 中的参数也不起作用。
  • 好的。我认为这是值得检查的,因为基于“如果问题是 X,那么我的程序应该早点崩溃”的调试有时会让我失败,因为“未定义的行为”有时只是做正确的事情:所以就我所知,双重删除有时可能有效,有时无效,原因不仅仅是像我这样的凡人。
  • 是的,我绝对同意。在查看其他任何地方之前,我仔细检查了这种同步问题。
【解决方案3】:

要调试它,您可以将deleted 成员添加到jobParams 类并将其设置为true,而不是实际删除该对象。然后查看jobParams 的每个方法中的deleted 标志,如果为真则抛出异常。然后看看哪里抛出异常。

【讨论】:

  • 这不完全是一门课,但我明白了你的意思。我会试试的。
  • 糟糕。将 JobFunction 更正为 jobParams。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-04
  • 2015-10-22
相关资源
最近更新 更多