首页 > 解决方案 > pthread函数realloc()中的C ++堆损坏:下一个大小无效

问题描述

嗨,我有一个在 Fedora25 上运行的跨平台 C++ 应用程序,它在执行大约一天后可以预见地崩溃,并出现错误 realloc(): invalid next size。

在此处输入图像描述

我已将问题缩小到特定的 pthread,该 pthread 向连接的客户端发送定期更新并清空传出消息队列。我在线程调用的函数内部为 char * 分配空间,我在发送后释放了空间。我通常不使用 C++,所以我在后台做 std::string 的东西,然后在需要时转换为 char *。我想确保我没有遗漏一些简单的东西以及有关如何重新构建或解决此问题的任何提示。

static void* MyPThreadFunc(void * params) {
  assert(params);
    MyAppServer *pAppServer = (MyAppServer *)params;    
    if(pAppServer != NULL) {    
       int loopCounter = 1;
       char* tempBuf;
       int tempBufLen;
       int tempDatSetDelay;
       
       while(true) {
          for(int i=0; i<pAppServer->GetUpdateDataSetCount();i++) {
             tempDatSetDelay = pAppServer->GetDataSetDelay(pAppServer->VecDatSets[i].name);
             if(tempDataSetDelay == 1 ||(tempDataSetDelay > 0 && loopCounter % tempDataSetDelay == 0)) {
                pAppServer->UpdateDataSetMsgStr(pAppServer->VecDataSets[i]);
                tempBuf = (char*)pAppServer->GetDataSetMsgStr(i); //returns const char*
                broadcast(pAppServer->Con,mg_mk_str(tempBuf));
                delete [] tempBuf;
             }//if           
          } //for
          
          //empty outgoing queue
          tempBuf = pAppServer->OUtgoingMsgQueue.peek(tempMsgLen);
          while(tempMsgLen>0) {
             broadcast(pAppServer->Con,mg_mk_str(tempBuf));
             pAppServer->OUtgoingMsgQueue.dequeue();             
             delete [] tempBuf;          
             
             tempBuf = pAppServer->OUtgoingMsgQueue.peek(tempMsgLen);                        
          }
                  
          sleep(1);
          loopCounter = loopCounter==std::numeric_limits<int>::max() ? 1 : ++loopCounter;   
       } //while       
       pAppServer=0;
    }
}

const char* AppServer::GetDataSetMsgStr(const int idx) {
        pthread_mutex_lock(&mLock);
        // Dynamically allocate memory for the returned string
        char* ptr = new char[VecDataSets[idx].curUpdateMsg.size() + 1]; // +1 for terminating NUL

        // Copy source string in dynamically allocated string buffer
        strcpy(ptr, VecDataSets[idx].curUpdateMsg.c_str());

        pthread_mutex_unlock(&mLock);
        // Return the pointer to the dynamically allocated buffer
        return ptr;
}
    
char* MsgQueue::peek(int &len) {
   char* myBuffer  = new char[512];
   len = 0;
   
   pthread_mutex_lock(&mLock);
   if(front==NULL) {
      len = -1;
          pthread_mutex_unlock(&mLock);
      return myBuffer;
     }
     
     len = front->len;
   strncpy(myBuffer,front->chars,len);
   pthread_mutex_unlock(&mLock);
   
   return myBuffer;
}

标签: c++heap-corruptionchar-pointer

解决方案


我不知道这是否能解决您的问题,但您的peek功能有几个问题:

问题 1:过早分配内存。


该函数的前两行无条件地执行此操作:

   char* myBuffer  = new char[512];
   len = 0;

但稍后,您的函数可以检测到len == -1,因此根本不需要分配内存。在您确定有理由分配内存之前,不应进行分配。


问题 2:在互斥体之外分配内存。

与上述问题 1 相关,您在互斥体建立之前分配内存。如果两个或多个线程尝试调用peek,则两个线程都有可能分配内存,从而导致竞争条件。

所有分配都应在互斥锁下完成,而不是在设置之前完成。

pthread_mutex_lock(&mLock);
// now memory can be allocated

问题 3:返回错误分配的内存。

由于该peek函数返回一个指向已分配内存的指针,然后调用者调用delete []此指针,因此返回 a 是完全有效的nullptr,而不必分配任何内存。

在您当前的peek函数中,即使出现错误,您也会返回分配的内存。这是此代码的建议重写:

if(front==NULL) 
{
   len = -1;
   pthread_mutex_unlock(&mLock);
   return nullptr;
}
char* myBuffer = new char[len];
//...

请注意,在确定有分配内存的理由之前,不需要进行分配。


问题 4:假设有 512 个字节要分配。

如果len大于 512 怎么办?您假设要分配的内存长度为 512,但如果len大于 512 怎么办?相反,您应该使用len来确定要分配多少字节,而不是硬编码的 512。


问题 5:不使用 RAII 来控制互斥锁。

如果在 中间peek抛出异常(如果new[]使用,可能会发生)怎么办?您将锁定互斥锁,现在如果该线程正在等待互斥锁解锁,则您有一个停滞的线程。

使用 RAII 来控制锁,基本上使用一个小结构,析构函数自动调用互斥锁的unlock函数。这样,无论返回的原因是什么peek,互斥锁都会自动解锁。

在 C++ 11 中,您有std::lock_guard使用 C++ 11 线程模型完成此任务。如果由于某种原因您必须坚持使用 pthread,您可以创建自己的 RAII 包装器:

struct pthread_lock_guard 
{
   pthread_mutex_lock* plock; 
   pthread_lock_guard(pthread_mutex_lock* p) : plock(p) {}
   ~pthread_lock_guard() { pthread_mutex_unlock(plock); }
};

然后你会像这样使用它:

char* MsgQueue::peek(int &len) 
{
   pthread_mutex_lock(&mLock);
   pthread_lock_guard lg(&mlock);
   char* myBuffer  = new char[512];
   len = 0;
   
   pthread_mutex_lock(&mLock);
   if(front==NULL) {
      len = -1;
      return myBuffer;
     }
     
     len = front->len;
   strncpy(myBuffer,front->chars,len);
   return myBuffer;
}

忽略指出的其他问题,您会看到没有更多调用来解锁互斥锁。lg当本地对象被销毁时,这一切都得到了照顾。


所以考虑到所有这些问题,这里是peek函数的最终重写:

struct pthread_lock_guard 
{
   pthread_mutex_lock* plock; 
   pthread_lock_guard(pthread_mutex_lock* p) : plock(p) {}
   ~pthread_lock_guard() { pthread_mutex_unlock(plock); }
};

char* MsgQueue::peek(int &len) 
{
   pthread_mutex_lock(&mLock);
   pthread_lock_guard lg(&mLock);
   if(front==NULL) 
   {
      len = -1;
      return nullptr;
   }
   len = front->len;
   char* myBuffer  = new char[len];
   strncpy(myBuffer,front->chars,len);
   return myBuffer;
}

请注意,这尚未编译,因此请原谅任何编译器错误。另请注意,这甚至可能无法解决您现在看到的问题。以上是为了说明您在原始问题中向我们展示的当前代码中的所有潜在缺陷,这些缺陷可能导致您看到的错误。

最后一点,您确实应该尽可能多地使用容器类,例如std::vector<char>or std::string,这样您就不会使用太多的原始动态内存处理。


推荐阅读