【问题标题】:Memory Leak During Push In A Queue推入队列期间的内存泄漏
【发布时间】:2012-09-29 04:57:07
【问题描述】:

我一直在为我的数据网络课程做一个项目,我遇到了内存泄漏,但我不明白为什么会发生。

顺便说一句,我知道 C 和 C++ 的混合很糟糕,但我对此无能为力,它基于类代码,我无法修改它,我知道这不是一个好方法方法来做到这一点,我需要使用 char* 作为必要的。

我的程序是多线程的,我处理这个结构:

typedef struct packetQueue
{
    char* buf;
    int length;

    packetQueue()
    {
        buf = nullptr;
        length = 0;
    }

    packetQueue(char* buffer, int len)
    {
        length = len;
        buf = new char[length + 1];
        memcpy(buf, buffer, len);
        buf[length] = '\0';
    }

    packetQueue(const packetQueue& other)
    {
        length = other.length;

        if (other.buf)
        {
            buf = new char[length + 1];
            memcpy(buf, other.buf, length);
            buf[length] = '\0';
        }
        else
        {
            buf = nullptr;
        }
    }

    packetQueue& operator=(const packetQueue& that)
    {
        if (this == &that)
        {
            return *this;
        }

        delete[] buf;

        length = that.length;

        if (that.buf)
        {
            buf = new char[length + 1];
            memcpy(buf, that.buf, length);
            buf[length] = '\0';
        }
        else
        {
            buf = nullptr;
        }
        return *this;
    }

    ~packetQueue()
    {
        delete[] buf;
        buf = nullptr;
    }

} PACKET;

在我的带有两个参数的构造函数中,我执行了该分配,因为我的队列的推送对我的结构进行了深层复制,就像我有我的复制构造函数一样,我已经处理了它。所以,我有一个线程(我一直在测试,VLD结果只是针对这个)。

DWORD _stdcall PHY_in_Thread(void* data)
{
    int numbytes, counter = 0;

    SOCKET hostSocket = *(SOCKET*) data; // Socket where the host receives

    struct sockaddr_in si_recvfrom;
    struct sockaddr_storage their_addr;
    socklen_t addr_len;

    addr_len = sizeof their_addr;

    while ( 1 )
{
    char* recBuf = new char[BUFLEN + 1];
    // Checks if it's any buffer on the socket to be processed
    if ( (numbytes = recvfrom(hostSocket, recBuf, BUFLEN, 0, (sockaddr*) &si_recvfrom, &addr_len)) == -1)
    {
        cerr << "Could not receive datagram." << endl;
        delete[] recBuf;
        closesocket(hostSocket);            
        WSACleanup();
        exit(0);
    }
    recBuf[numbytes] = '\0'; // append NULL to the end of the string

    char* temporalBuffer = new char[numbytes - CHECKSUM_MAX_SIZE + 1];
    memcpy(temporalBuffer, recBuf, numbytes - CHECKSUM_MAX_SIZE);
    temporalBuffer[numbytes - CHECKSUM_MAX_SIZE] = '\0';

    char extractedChecksum[CHECKSUM_HEX_SIZE + 1];
    DWORD crcBuffer = crc32buf(temporalBuffer, numbytes- CHECKSUM_MAX_SIZE); // Calculates the CRC32 checksum
    _snprintf(extractedChecksum, 8 , "%08lX", crcBuffer); // Prints the string in a buffer
    extractedChecksum[CHECKSUM_HEX_SIZE] = '\0';

    delete[] temporalBuffer;

    string strExtractedChecksum = extractedChecksum; // Copies the array in a string
    transform(strExtractedChecksum.begin(), strExtractedChecksum.end(), strExtractedChecksum.begin(), upper); // Uppercase the string

    // Array for store the checksum of the packet
    char readChecksum[CHECKSUM_MAX_SIZE + 1];

    // Store the checksum of the packet in local variable
    memcpy( readChecksum, &recBuf[numbytes - CHECKSUM_MAX_SIZE], CHECKSUM_MAX_SIZE);    
    readChecksum[CHECKSUM_MAX_SIZE] = '\0';

    std::stringstream stream;
    string strReadChecksum;
    for (int i = 0; i < CHECKSUM_MAX_SIZE; i++ )
    {
        int number = static_cast<int>(readChecksum[i]); // Casts every character of the checksum array

        if ( readChecksum[i] <= -1 ) // In case the int value it's negative adds the constant value to make that recognizable
        {
            number += 256;
        }

        // Convert the decimal number in a hex representation
        stream.str("");
        stream << hex << number;

        if ( stream.str().length() < 2 ) // In case it's a number less than 10, adds a 0 at the beginning
        {
            strReadChecksum += "0" +  stream.str();
        }
        else
        {
            // Working out the presentation of the number
            strReadChecksum += stream.str();
        }
    }

    std::transform(strReadChecksum.begin(), strReadChecksum.end(), strReadChecksum.begin(), upper); // Uppercase the string
    strReadChecksum[CHECKSUM_HEX_SIZE] = '\0';

    cout << "[PI] Frame #" << counter <<" received ("<< numbytes <<" bytes). " << endl;
    if ( !strcmp(strReadChecksum.c_str(), extractedChecksum) ) // Checks if the CRC are equal
    {
        cout << "[CRC] Checksum OK: 0x" << extractedChecksum << endl;
    }
    else
    {
        cout << "[CRC] Checksum failure: 0x" << extractedChecksum << endl;
    }

    // Push the packet in the MAC_in_queue to be processed
    MAC_in_queue.push(PACKET(recBuf, numbytes));
    recBuf = nullptr;
    counter++;

            break;   // Just for test one packet
}

MAC_in_queue.clear();

return 0;

}

但是当我执行这个线程并向这个线程发送一些东西以存储在这个队列中时,就会出现泄漏。在此执行中,只有一项可以使事情变得简单。

---------- Block 29 at 0x0068F718: 264 bytes ----------
  Call Stack:
    d:\program files (x86)\microsoft visual studio 11.0\vc\include\concurrent_queue.h (402): Host.exe!Concurrency::concurrent_queue<packetQueue,std::allocator<packetQueue> >::_Allocate_page + 0xF bytes
    f:\dd\vctools\crt_bld\self_x86\crt\src\concurrent_queue.cpp (113): MSVCP110D.dll!Concurrency::details::_Micro_queue::_Push + 0xD bytes
    f:\dd\vctools\crt_bld\self_x86\crt\src\concurrent_queue.cpp (240): MSVCP110D.dll!Concurrency::details::_Concurrent_queue_base_v4::_Internal_move_push
    d:\program files (x86)\microsoft visual studio 11.0\vc\include\concurrent_queue.h (581): Host.exe!Concurrency::concurrent_queue<packetQueue,std::allocator<packetQueue> >::push + 0xF bytes
    d:\users\silex rpr\documents\visual studio 2012\projects\project3\hoster\host.cpp (638): Host.exe!PHY_in_Thread + 0x3D bytes
    0x7474339A (File and line number not available): kernel32.dll!BaseThreadInitThunk + 0x12 bytes
    0x76EC9EF2 (File and line number not available): ntdll.dll!RtlInitializeExceptionChain + 0x63 bytes
    0x76EC9EC5 (File and line number not available): ntdll.dll!RtlInitializeExceptionChain + 0x36 bytes
  Data:
    00 00 00 00    01 00 00 00    60 F8 68 00    80 00 00 00     ........ `.h.....
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........

但我不明白这些数据泄漏在哪里,我希望我说清楚了。

先谢谢

【问题讨论】:

  • pushclear 方法有什么作用?嗯......我可以猜到他们应该做什么,但看起来泄漏是在 push 方法中(好像 clear 可能没有清除队列)。
  • 是的,我正在使用 Visual Studio 2012 的 concurrent_queue,但这清楚它在我的循环之外,所以当线程结束时,清理所有数据,但即使这样做它也会不断向我发送泄漏。
  • 你应该给PACKET添加一个赋值操作符,因为你不能冒险复制buf指针导致双重释放。
  • 是的,我已经加了一个,报错一模一样。
  • 是否有其他线程同时调用MAC_in_queue.try_pop()?如果是,它可能会导致未定义的行为,因为 concurrent_queue::clear 不是并发安全的。

标签: c++ c memory-leaks queue


【解决方案1】:

我只能建议我之前的其他人。 泄漏在您的 while 循环中,分配了 recBuf 但没有适当的删除。 详情:

//Allocates memory,ok
char* recBuf = new char[BUFLEN + 1];

//free memory when exits, ok
if ( (numbytes = recvfrom(...)) == -1)
{
    cerr << "Could not receive datagram." << endl;
    delete[] recBuf;
    ...
}

//do something with it

//And here is the problem
MAC_in_queue.push(PACKET(recBuf, numbytes));
//With this you call this constructor
// packetQueue(char* buffer, int len)
// which allocates the same amount of memory, and copies the contents of recBuf
//So, there is a +1 memory allocation

// This is not deallocate memory :)
recBuf = nullptr;

我建议将recBuf内存分配移到循环之外,并在最后删除它(使用delete[] recBuf)

delete[] recBuf;
MAC_in_queue.clear();

,有了这个,你不会每次都不必要地分配,因此会更快一些。 或者如果你喜欢这种方式,你应该插入这样的代码:

 delete[] recBuf;
 recBuf = nullptr;

【讨论】:

    【解决方案2】:

    在我看来,您的 packetQueue 并没有做太多事情(除了内存管理,它不能正常工作)。

    您可以使用标准容器来实现您的类。假设您不想要类的引用计数实现,我们需要排除 std::string,但您可以使用 std::vector。例如:

    class PacketQueue
    {
    public:
        PacketQueue() : buf_() {}
        PacketQueue(char* buffer, int len) : buf_(buffer,buffer + len) {}
        //here your function to return the '\0' terminated buffer
        //and all the other stuff that you need
    private:
        std::vector<char> buf_;
    };
    

    请注意,默认的:复制构造函数、赋值运算符和析构函数都可以,你不需要实现这些。基本上内存管理被封装到 std::vector 中。

    这也是强大的异常安全保证。你的代码不是。

    【讨论】:

    • 当然,在现代 C++ 中,我们可以使用 std::vector 并对此感到满意。但请阅读问题:“顺便说一下,我知道 C 和 C++ 的混合很糟糕,但我对此无能为力,它基于类代码,我无法修改它,我知道这不是一个好方法,我需要根据需要使用 char*。”
    • 你并不是说你不能使用标准容器。您还提到您无法更改类,但我认为它不是 PacketQueue,因为您向其中添加了赋值运算符。在您的代码中,您使用字符串(std::string?)和 std::transform,所以我认为您也可以访问 std::vector。您是 packetQueue 的所有者吗?可以使用标准容器吗?
    • 这是 OP 应该澄清的。如果他可以使用std::vector和现代C++并放弃原始C风格,那么有几个改进点,包括线程函数体中的一些new[](例如@ 987654325@, temporalBuffer),可以使用std::vector更新。
    【解决方案3】:

    while ( 1 )循环的开头PHY_in_Thread()你分配recBuf

    char* recBuf = new char[BUFLEN + 1];
    

    但是在循环体的末尾,你错过了释放它;你只是因为 recBuf = nullptr; 分配。

    相反,尝试在循环体末尾正确删除recBuf

    delete [] recBuf;
    recBuf = nullptr;
    

    【讨论】:

      【解决方案4】:

      您已使用以下命令在 while(1) 循环的开头分配了 recBuf:

      char* recBuf = new char[BUFLEN + 1];
      

      然后你将它传递给一个临时变量 PACKET(recBuf, numbytes),它只存在于一行代码的范围内:

      MAC_in_queue.push(PACKET(recBuf, numbytes));
      

      现在,假设 MAC_in_queue 是这样初始化的:

      concurrent_queue<PACKET> MAC_in_queue
      

      这意味着您将同时使用复制构造函数和 operator=。因为在这两个命令中都没有释放为 buf 分配的内存,所以存在泄漏。

      请阅读this article,其中有一个与您类似的示例。看看他们是如何实现 Copy 构造函数和操作符的 =

      【讨论】:

      • 我刚刚在我的函数中这样做了,我在赋值运算符的第一行放置了一个断点,但它永远不会去那里,它永远不会在那个点中断并且泄漏不断发生。
      【解决方案5】:

      在分配“that”的副本之前,您的赋值运算符不会释放 PACKET 中的旧内容。因此,如果您覆盖某些容器代码中的条目,则被覆盖的项目所持有的缓冲区将会泄漏。这是泄漏,但我当然无法证明这是您所看到的泄漏。

      【讨论】:

      • 我只是在我的函数中这样做了,我在赋值运算符的第一行放置了一个断点,但它永远不会去那里,它永远不会在那个点中断并且泄漏不断发生
      • 看起来你改变了你的代码,现在我想说泄漏正是上面@Mr.C64所说的,你永远不会清理原始缓冲区“recBuf”,因为你错过了删除的调用[] recBuf;
      • 澄清一下:就您的代码现在而言,您从 recBuf 构造了一个临时 PACKET,它在构造函数中从 buffer+len 复制缓冲区。如果您使用的容器类(队列)是正确的,这将被进一步复制并在以后释放这些副本。您创建的临时 PACKET 在调用“push(...)”返回后立即销毁时释放其缓冲区副本。但是在成功的情况下,原始的“recBuf”从未被释放。
      猜你喜欢
      • 1970-01-01
      • 2021-04-30
      • 2021-01-31
      • 2016-07-04
      • 2014-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多