在给定约束的情况下,为什么这个 C++ 程序会发生内存泄漏以及如何解决它?

问题描述 投票:0回答:5

这是我在实际代码中遇到的问题的最小工作示例。

#include <iostream>

namespace Test1 {
    static const std::string MSG1="Something really big message";
}

struct Person{
    std::string name;
};

int main() {
    auto p = (Person*)malloc(sizeof(Person));
    p = new(p)Person();
    p->name=Test1::MSG1;

    std::cout << "name: "<< p->name << std::endl;
    
    free(p);
    
    std::cout << "done" << std::endl;

    return 0;
}

当我编译它并通过 valgrind 运行它时,它给了我这个错误:

definitely lost: 31 bytes in 1 blocks


约束

  1. 我一定会在上面的例子中使用
    malloc
    ,因为在我的真实代码中,我在我的C++项目中使用了一个C库,它在内部使用了这个
    malloc
    。所以我无法摆脱
    malloc
    用法,因为我没有在我的代码中的任何地方明确地这样做。
  2. 我需要在我的代码中一次又一次地重新分配
    std::string name
    Person
c++ memory-leaks valgrind dynamic-memory-allocation placement-new
5个回答
27
投票

逐行代码的重要部分......

为一个Person对象分配内存:

auto p = (Person*)malloc(sizeof(Person));

通过调用其构造函数在已分配的内存中构造一个 Person 对象:

p = new(p)Person();

释放通过 malloc 分配的内存:

free(p);

通过放置

new
调用构造函数创建一个
std::string
。该字符串将在析构函数中被销毁,但永远不会调用析构函数。
free
不调用析构函数(就像
malloc
不调用构造函数一样)。

malloc
只分配内存。 Placement new 仅在已分配的内存中构造对象。因此你需要在调用
free
之前调用析构函数。这是我知道明确调用析构函数正确和必要的唯一情况:

auto p = (Person*)malloc(sizeof(Person));
p = new(p)Person();
p->~Person();
free(p);

18
投票

之前必须手动调用析构函数

free(p);

p->~Person();

std::destroy_at(p)
,这是一回事。


7
投票

如其他答案中所述,泄漏的来源是

name
成员的析构函数
Person
没有被调用。当调用
Person
的析构函数时,通常会隐式调用它。但是,
Person
永远不会被破坏。
Person
实例的内存只需使用
free
释放。

所以,就像您必须在

new
之后显式调用带有放置
malloc
的构造函数一样,您还需要在
free
之前显式调用析构函数。

您还可以考虑重载

new
delete
运算符。

struct Person {
    std::string name;
    void * operator new (std::size_t) { return std::malloc(sizeof(Person)); }
    void operator delete (void *p) { std::free(p); }
};

这样,您可以正常使用

new
delete
,而在下面他们将使用
malloc
free

int main (void) {
    auto p = new Person;
    //... 
    delete p;
}

这样,您可以更自然地使用智能指针。

int main (void) {
    auto p = std:make_unique<Person>();
    //... unique pointer will delete automatically
}

当然,您可以将

unique_ptr
与自定义删除器一起使用,并显式调用
malloc
free
,但这会麻烦得多,并且您的删除器仍然需要知道显式调用析构函数以及。


0
投票

如果您必须将此函数与需要您使用默认设置以外的一些初始化和清理的库一起使用,例如

malloc
/
free
,一种方法是定义一个新的删除器:

#include <memory>
#include <stdexcept>
#include <stdlib.h>
#include <string>

struct Person{
    std::string name;
};

struct PersonDeleterForSomeLib {
  constexpr void operator()(Person* ptr) const noexcept {
    ptr->~Person();
    free(ptr);
  }
};


Person* Person_factory() // Dummy for the foreign code.
{
  Person* const p = static_cast<Person*>(malloc(sizeof(Person)));
  if (!p) {
    throw std::runtime_error("Out of memory.");
  }
  new(p) Person();
  return p;
}

这让您可以安全地使用:

const auto p =
  std::unique_ptr<Person, PersonDeleterForSomeLib>(Person_factory());

具有自动内存管理。您可以从函数返回智能指针,析构函数和

free()
都将在其生命周期结束时被调用。您也可以通过这种方式创建
std::shared_ptr


-2
投票

以前的答案忽略了 std::string 默认分配器自动处理内存管理,因此没有充分的理由首先将 p 放在堆上。这同样适用于其他标准库集合。 https://en.cppreference.com/w/cpp/string/basic_string


    int main() {
        Person p;
        p.name=Test1::MSG1;
    
        std::cout << "name: "<< p.name << std::endl;
        
        std::cout << "done" << std::endl;
    
        return 0;
    }

或者,如果您需要为某些多态性使用指针,您可以使用智能指针来帮助您进行内存管理。

我听说的一些广泛的规则是你不应该在 C++ 代码中使用 malloc。而且你真的不应该混合使用 malloc 和 new。以前的答案已经说明了如何完成,但是您确实必须阅读手册才能了解

p->~Person();
.

© www.soinside.com 2019 - 2024. All rights reserved.