"Strange" 内存泄漏 - TCP 网络

"Strange" memory leaks - TCP Network

我有很多 classes 构建 TCP 网络 - 使用 boost::asio,使用 Packets 进行传输。 (Packet 的基数 class 是 std::vector<char>

我以为我已经修复了所有可能的内存泄漏,但后来我只是在关闭客户端之前关闭了服务器 - 这应该不会造成任何问题 - _CrtDumpMemoryLeaks 报告了内存泄漏。所以我将 Visual Leaks Detector 添加到项目中并检查了这些内存泄漏发生的位置。

void dumpMemoryLeaks(void) { CrtDumpMemoryLeaks(); }
//and in main:
atexit(dumpMemoryLeaks);

回头查看报告 VLD 内存泄漏的行,对我来说似乎有点……奇怪。

我有一个 class AsyncAcceptor ,我在构造函数初始化列表中分配 new BoostSocket - 原始指针(普通和 SSL 套接字的包装器)。在 AsyncAcceptor 的析构函数中,原始指针如果有效则被正确删除。

_acceptor = new AsyncAcceptor(service, bindIP, port); //No memory leak for this pointer
//...
_acceptor->AsyncAcceptManaged(_OnSocketAccept); //Function pointer

AsyncAcceptManaged 内部,服务器正在等待连接并传递另一个指针 - 一次内存泄漏。

//Set socket properties
//Call handler function passed as parameter
handler(_socket); //Raw pointer passed to handler
_socket = nullptr;
_socket = new BoostSocket(_acceptor.get_io_service()); //with ctx for SSL, if defined
//If the acceptor is not closed, it calls `AsyncAcceptManaged` again.

现在处理函数。

void OnSocketOpen(BoostSocket *sock)
{
    //set additional socket options and check for error
    //Note: SocketType is template parameter of class - allowing different socket types
    std::shared_ptr<SocketType> newSocket(new SocketType(sock));
    delete sock;
    sock = nullptr;
}

SocketType 总是有一个基础 class 具有相同的构造函数重载 BoostSocket *.

Socket(BoostSocket *socket)
    : _socket(std::move(*socket))
{
    //...
}

BoostSocket 中,成员 _socket 是 std::unique_ptr<SocketType>,其中 SocketType 是 tcp::socketboost::asio::ssl::stream<tcp::socket>.

的类型定义
BoostSocket(BoostSocket &&s)
    : _socket(std::move(s._socket))
{ }

Visual Leak Detector 指向此堆栈:

  1. AsyncAcceptor::AsyncAcceptManaged
  2. OnSocketOpen - 处理程序
  3. shared_ptr-内存(452)-explicit shared_ptr(_Ux *_Px)
  4. void _Resetp(_Ux *_Px)

也有类似的"strange"报道。


Packet p;
//add data & add Packet to a std::queue
QueuePacket(Packet &&p);

//Inside QueuePacket:
_queue.push(std::move(p));

内存泄漏:

  1. 队列 (111) - void push(value_type&& _Val)
  2. 双端队列 (1181) - void push_back(value_type&& _Val)

内存泄漏(_socket是一个std::unique_ptr):

BoostSocket(boost::asio::io_service &ioService)
    : _socket(new SocketType(ioService)) //<--- here
{ }

内存泄漏(m_Member是一个std::shared_ptr):

m_Member = std::move(std::shared_ptr<StoredClass>(new StoredClass(shared_from_this()) ) );

大量内存泄漏(_readBuffer 是一个 std::vector<char>):

_readBuffer.resize(static_cast<std::size_t>(DataUnits::DATA_UNIT_MB) * 5); //resize to 5 MB

我假设最后一次内存泄漏是由于内存泄漏报告中的 Socket 没有被正确销毁造成的。但是我无法想象问题可能是什么。我的原始指针得到了正确处理,对于其他任何事情我都使用智能指针。

Visual Leak Detector detected 17 memory leaks (5244907 bytes).

这是 VLD 的正确报告吗?我似乎无法找到导致一切的内存泄漏。任何提示或解决方案表示赞赏。

如果您担心它是否可能是误报,您需要 运行 程序的 "core logic" 在调试器的循环中。一次以兆字节计算,这应该可以很快确认内存是否泄漏。

关于你程序的实际设计:

My raw pointers are handled properly and for anything else I use smart pointers.

...

void OnSocketOpen(BoostSocket *sock)
{
    //set additional socket options and check for error
    //Note: SocketType is template parameter of class - allowing different socket types
    std::shared_ptr<SocketType> newSocket(new SocketType(sock));
    delete sock;
    sock = nullptr;
}

该声明和我正在采样的代码似乎相互矛盾。

一般来说,对于 C++11/C++14,通常的建议是永远不要,永远 处理原始指针,永远不要,ever 处理明确的 newdelete 关键字。显然,没有任何规则是 100% 绝对可靠的(我建议你在电脑显示器上用记号笔写下),但在这种情况下,我会说它在 99.99...% 的时间里仍然是正确的。

所以每次你处理原始指针时,你要么想要将它们替换为 std::unique_ptr(如果它拥有该对象)或 std::shared_ptr(如果多个对象拥有它) ,如果您还没有这样做的话。我看到您已经对某些对象使用了 std::unique_ptrstd::shared_ptr;确保您为 所有 的指针执行此操作。

其次,将每次使用 new 替换为适当的 std::make_uniquestd::make_shared。删除所有对 delete 的引用,除非您正在编写客户删除程序;即便如此,请确保这确实是您所需要的。

第三,要非常注意代码的语义:

m_Member = std::move(std::shared_ptr<StoredClass>(new StoredClass(shared_from_this()) ) );

根据一般经验,没有理由 "move" std::shared_ptr。 The reason std::shared_ptr exists is to allow for safe copy semantics of pointers, and in the scenarios where moves are the appropriate choice, the Compiler is usually smart enough to figure it out for you.这段代码本身可能没问题(尽管 std::move 是多余的)但您可能还需要查看其他代码区域。

我无法指出任何可能是罪魁祸首的特定代码(特别是因为我们没有在此处列出完整的代码)但是如果您解决所有这些问题,它很可能会解决内存泄漏(s).

显然套接字没有自动销毁。在停止并加入处理新 Sockets/Connections 的线程 Sockets/Connections 完成更新循环后,我通过在循环中手动关闭所有左侧套接字来解决此问题。

代码如下所示:

//_sockets - std::set with std::shared_ptr<SocketType>
if (_stopped) //std::atomic
{
    for (i = _sockets.begin(); i != _sockets.end();)
    {
        if ((*i)->IsOpen() && (*i)->IsSocketOpen())
        {
            (*i)->CloseSocket();
            SocketRemoved(*i); //Do work when client gets disconnected
            --_connections; //std::atomic
        }
        _sockets.erase(i++);
    }
}

VLD 不会报告任何内存泄漏,尽管 _CrtDumpMemoryLeaks 有自己的意见。

No memory leaks detected.
Visual Leak Detector is now exiting.