std::string 的内存泄漏取决于字符串大小

1 c++ string valgrind c++11

我在 C++ 中遇到内存泄漏,具体取决于字符串大小。在下面的代码中,如果字符串大小小于 15,valgrind 不会显示泄漏,但是如果字符串大小大于 15,则会出现内存泄漏:

#include <string>
#include <memory>
#include <iostream>

class AuthBase {
public:
    virtual void foo() = 0;
};

class APIAuthetication : public AuthBase {
public:
    APIAuthetication(const std::string api_key, const std::string api_secret) : m_api_key(api_key),
                m_api_secret(api_secret) {}
    
    virtual ~APIAuthetication() {}

    void foo() {}
private:
    const std::string m_api_key;
    const std::string m_api_secret;
};

class ClientBase {
public:
    virtual void bar() = 0;
};

class Client: public ClientBase {
public:
    Client(AuthBase* auth) : m_auth(auth) {}
    ~Client() {}

    void bar() {}
private:
    std::unique_ptr<AuthBase> m_auth;
};

class ClientAPI : public Client {
public:
    ClientAPI(const std::string api_key, const std::string api_secret) : 
                Client((new APIAuthetication(api_key, api_secret))) {}
    virtual ~ClientAPI() {}
};

int main(void) {
    /* This runs fine. String size == 15 */
    ClientAPI cli("123456789101112", "123456789101112");
    /* Memory leak. String size > 15 */
    ClientAPI cli_leak("1234567891011121", "1234567891011121");
    return 0;
}
Run Code Online (Sandbox Code Playgroud)
==17060== HEAP SUMMARY:
==17060==     in use at exit: 34 bytes in 2 blocks
==17060==   total heap usage: 9 allocs, 7 frees, 72,950 bytes allocated
==17060== 
==17060== Searching for pointers to 2 not-freed blocks
==17060== Checked 111,624 bytes
==17060== 
==17060== 17 bytes in 1 blocks are definitely lost in loss record 1 of 2
==17060==    at 0x4C3217F: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==17060==    by 0x4F6513C: void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<char*>(char*, char*, std::forward_iterator_tag) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25)
==17060==    by 0x1091B3: APIAuthetication::APIAuthetication(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) (in /home/rogerio/code-studies/curl-examples/weird)
==17060==    by 0x10937C: ClientAPI::ClientAPI(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) (in /home/rogerio/code-studies/curl-examples/weird)
==17060==    by 0x108FD3: main (in /home/rogerio/code-studies/curl-examples/weird)
==17060== 
==17060== 17 bytes in 1 blocks are definitely lost in loss record 2 of 2
==17060==    at 0x4C3217F: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==17060==    by 0x4F6513C: void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<char*>(char*, char*, std::forward_iterator_tag) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25)
==17060==    by 0x1091CA: APIAuthetication::APIAuthetication(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) (in /home/rogerio/code-studies/curl-examples/weird)
==17060==    by 0x10937C: ClientAPI::ClientAPI(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) (in /home/rogerio/code-studies/curl-examples/weird)
==17060==    by 0x108FD3: main (in /home/rogerio/code-studies/curl-examples/weird)
==17060== 
==17060== LEAK SUMMARY:
==17060==    definitely lost: 34 bytes in 2 blocks
==17060==    indirectly lost: 0 bytes in 0 blocks
==17060==      possibly lost: 0 bytes in 0 blocks
==17060==    still reachable: 0 bytes in 0 blocks
==17060==         suppressed: 0 bytes in 0 blocks
==17060== 
==17060== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
==17060== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
Run Code Online (Sandbox Code Playgroud)

如果字符串大小 < 15,则不会泄漏:

==17211== 
==17211== HEAP SUMMARY:
==17211==     in use at exit: 0 bytes in 0 blocks
==17211==   total heap usage: 2 allocs, 2 frees, 72,776 bytes allocated
==17211== 
==17211== All heap blocks were freed -- no leaks are possible
==17211== 
==17211== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
==17211== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Run Code Online (Sandbox Code Playgroud)

我知道如果一个字符串小于 16 个字节,它不会分配内存并使用字符缓冲区(优化),但对我来说这种内存泄漏没有意义。有谁知道我的代码有什么问题吗?

Rem*_*eau 9

您已经知道为什么泄漏的大小取决于每个字符串的内容(短字符串优化与动态分配)。

泄漏本身是因为AuthBase没有虚拟析构函数。

std::unique_ptrin 中的成员Client被销毁时,它将调用delete它所AuthBase*持有的指针。并且由于AuthBase的​​(默认生成的)析构函数不是虚拟的,因此APIAuthetication不会调用 的析构函数,因此它的 2 个std::string成员不会被销毁,从而泄漏它们为自己分配的任何内存。

您需要添加一个虚拟析构函数AuthBase

class AuthBase {
public:
    virtual ~AuthBase() = default;
    ...
};
Run Code Online (Sandbox Code Playgroud)

然后将APIAuthetication的析构函数更改override为该析构函数:

class APIAuthetication : public AuthBase {
public:
    ...
    ~APIAuthetication() override = default;
    ...
};
Run Code Online (Sandbox Code Playgroud)

ClientBase您的类及其派生类也是如此。

class ClientBase {
public:
    virtual ~ClientBase() = default;
    ...
};

class Client : public ClientBase {
public:
    ...
    ~Client() override = default;
    ...
};

class ClientAPI : public Client {
public:
    ...
    ~ClientAPI() override = default;
};
Run Code Online (Sandbox Code Playgroud)

经验法则:如果一个类可以派生,它应该声明(或继承)一个虚拟析构函数。


Client附带说明一下,的构造函数采用原始指针作为输入并不是一个好主意AuthBase*。应该用 astd::unique_ptr<AuthBase>来代替。在现代 C++ 编码中,最好尽可能避免直接调用new/ 。delete可以这么说,让智能指针从摇篮到坟墓都完成他们的工作。

class Client : public ClientBase {
public:
    Client(std::unique_ptr<AuthBase> auth) : m_auth(std::move(auth)) {}
    ...
private:
    std::unique_ptr<AuthBase> m_auth;
};

class ClientAPI : public Client {
public:
    ClientAPI(const std::string &api_key, const std::string &api_secret) : 
        Client(std::make_unique<APIAuthetication>(api_key, api_secret)) {}
    ...
};
Run Code Online (Sandbox Code Playgroud)