【问题标题】:Casting to void* with potentially sketchy behavior以可能粗略的行为强制转换为 void*
【发布时间】:2016-05-01 23:36:43
【问题描述】:

几周以来,我一直在尝试修复在任务调度应用程序中可能粗略地使用 void* 转换的问题。请注意,我没有收到编译器错误,但调度程序在几个小时后崩溃(出于某种未知原因)。考虑程序中的以下代码 sn-ps:

main:

CString* buffer = new CString(temp);
parameters.set("jobID", (void *) buffer);
runJob(parameters);

另外,VGridTaskParam 类如下:

class VGridTaskParam{
    map<CString, void *> p; // maps from CString to a pointer that is not known 
public:
    void * get(CString name){
        return p[name]; // returns the map value of the name which is an unknown pointer 
    }
    void set(CString name, void * data){
        p[name] = data; // sets the mpa value given a particular key 
    }
};

runJob(VGridTaskParam parameters) 函数中的一些工作片段是:

void runJob(VGridTaskParam parameters)
{
        CString JIDstr; // job ID string 
//get job ID as CString
        CString* pJID = (CString*)parameters.get("jobID");
        JIDstr = CString(*pJID);
        delete pJID; ******************************
}

一些问题:最后delete 行(标有几个星号)是否删除了在主程序中创建的内存分配?在这种情况下,我是否需要使用 void* 强制转换。请注意,每当我运行作业时,我都会生成一个新线程。有人可以建议解决此问题的潜在方法吗?我应该看什么来解决这个问题?

【问题讨论】:

  • 如果它在几个小时后崩溃而不是立即崩溃,那么您可能内存不足,因此您对delete 的直觉是正确的。诚然,这里看起来不错,所以我认为问题出在 runJob 的其他地方或它调用的方法中。也许有更多的内存分配,或者您的CString* 可能以另一种方式被释放,导致双重删除。
  • 我不能说确切的错误是什么,但我可以说动态分配对象并要求手动删除它是要求内存泄漏。如果可能的话,我的建议是将参数数据结构的保持值类型从 (void *) 更改为 unique_ptr (或者甚至只是简单的旧的按值存储的 Cstring),以便所需的删除是自动为您完成的(当然,删除您对删除的明确调用)。这可能会解决问题。
  • 我看不出有任何理由在这里使用void* 强制转换,但是,它们似乎并没有导致崩溃。我建议从 C++ 程序中删除 void* 算术,看看你是否需要任何动态内存管理——也可能删除它。这将使代码更清晰,更容易推理,因此可以更容易地找到错误(如果它仍然存在)。
  • 在您的getset 方法中,如果name 不作为键存在,您将在地图中创建孔。 std::map::operator[ ] 创建一个条目,如果它不存在的话。而是使用map::find()map::count() 来确定该项目是否存在。

标签: c++ memory-management memory-leaks void-pointers


【解决方案1】:

您的delete 是正确的。它在指针的地址处删除指针类型大小的已分配内存块。因此,当您分配 CStringnew 时,您将 delete 完全相同的内存量。

挂断是当您删除 未分配 内存时。我可以通过多种方式在您当前的代码中看到这种情况:

  1. runJob 请求不在 map 中的键(这将返回默认初始化的 CString* 作为新创建的值。)
  2. 您在 map 中先前删除的项目上调用 runJob(此值已被释放,再次删除将是非法的。)
  3. 我们看不到您的所有代码,但如果有可能将这些指针 delete 在其他地方,那么那里也可能存在双重清理问题。

您应该更加防御性地编写地图,例如:

const void* get(const CString& 名称) const { 返回 p.find(name) == p.cend() ?空指针:p[名称]; // 返回名称的映射值,它是一个未知指针 }

void set(const CString& name, void* data) {
    void* toOverwrite = get(name);

    if(toOverwrite != nullptr) {
        delete toOverwrite;
    }
    p[name] = data; // sets the mpa value given a particular key 
}

void remove(const CString& name) {
    if(p.find(name) != p.end()) {
        p.erase(name);
    }
}

在您的函数中,您需要更改为测试get 的返回值是否为nullptr,然后再对结果进行操作,而不是delete,您需要调用remove。这将所有p 的修改保留在类本地,从而保证map 的正确维护。

【讨论】:

    【解决方案2】:

    我看到了两件让我有点吃惊的事情。

    1) runJob 按值获取 VGridTaskParam 类型的参数。也许你想要一个参考。不过,这无法解释崩溃的原因。

    2) 即使您删除了与指针关联的内存,您似乎也从未从地图中删除任何内容。因此,您以后可能会使用指针值。作为取消引用或双重删除。

    3) 您没有对地图中存在的键进行任何检查。

    【讨论】:

      【解决方案3】:

      几个可能的问题:

      如果你向同一个键写入两次会发生什么?这将导致内存泄漏。 可能的解决方案很少,例如:

      void set(CString name, void * data){
          if(p[name]!=NULL) 
            delete p[name]; 
          p[name] = data; // sets the mpa value given a particular key 
      }
      

      另一件事:您是在运行多线程还是单线程?对于多线程,您应该同步 get 和 set 方法以防止在操作中间更改指针。

      最后,如果找不到密钥会怎样?你应该检查返回的指针。也可以在使用后从集合中移除或将其设为空。

      void runJob(VGridTaskParam parameters)
      {
          CString JIDstr; // job ID string 
          //get job ID as CString
          CString* pJID = (CString*)parameters.get("jobID");
          if(pJID){
             parameters.set("jobID",NULL);
             JIDstr = CString(*pJID);
             delete pJID; ******************************
          }
      }
      

      【讨论】:

        【解决方案4】:

        最后delete 行(标有几个星号)是否删除了在主程序中创建的内存分配?

        是的。

        在这种情况下我是否需要使用 void* 转换?

        不,那是多余的。任何指针都可以转换为 void* 而无需显式转换。

        有人可以建议解决此问题的潜在方法吗?我应该看什么来解决这个问题?

        如果没有MCVE,我无法提出修复建议。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-06-17
          • 1970-01-01
          • 2020-06-27
          • 2012-04-12
          • 2013-12-25
          • 1970-01-01
          • 2015-08-08
          • 2011-01-07
          相关资源
          最近更新 更多