提问人:Anurag Vohra 提问时间:3/1/2023 最后编辑:Peter MortensenAnurag Vohra 更新时间:3/6/2023 访问量:5375
为什么这个程序中存在内存泄漏,我该如何解决它,给定约束(对包含 std::string 的对象使用 malloc 和 free)?[复制]
Why is there a memory leak in this program and how can I solve it, given the constraints (using malloc and free for objects containing std::string)? [duplicate]
问:
这是我在实际代码中面临的问题的最小工作示例。
#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 运行它时,它给了我这个错误:
绝对丢失:1 个区块 31 个字节
约束
- 我一定会在上面的示例中使用,因为在我的实际代码中,我在 C++ 项目中使用了一个 C 库,它在内部使用它。因此,我无法摆脱使用,因为我没有在代码中的任何地方明确地使用它。
malloc
malloc
malloc
- 我需要在我的代码中一次又一次地重新分配。
std::string name
Person
答:
您必须在以下之前手动调用析构函数:free(p);
p->~Person();
或者,这是一回事。std::destroy_at(p)
代码中的重要部分逐行...
为一个 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);
评论
Person
Person::name
std::string
Person
std::string
malloc
operator delete
new
malloc
delete
new
malloc
delete
free
正如其他答案中提到的,泄漏的来源是 成员的析构函数没有被调用。当调用析构函数 for 时,通常会隐式调用它。但是,永远不会被破坏。实例的内存只需使用 释放。name
Person
Person
Person
Person
free
因此,正如您必须在 之后使用位置显式调用构造函数一样,您还需要在 之前显式调用析构函数。new
malloc
free
还可以考虑重载 and 运算符。new
delete
struct Person {
std::string name;
void * operator new (std::size_t sz) { return std::malloc(sz); }
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
评论
malloc
new
operator new / delete
new
delete
new
delete
assert
class
正如其他人所提到的,由 成员分配的动态内存仅由 析构函数释放,析构函数不调用。Person
~Person
free()
如果您必须将此函数与需要一些初始化和清理的库一起使用,而不是默认的库,例如此处,一种方法是定义一个新的删除器,供标准 libray 智能指针使用: 即使使用您自己未分配的内存块,这也将起作用。
#include <memory>
#include <new> // std::bad_alloc
#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::bad_alloc();
}
new(p) Person();
return p;
}
这使您可以安全地使用:
const auto p =
std::unique_ptr<Person, PersonDeleterForSomeLib>(Person_factory());
具有自动内存管理功能。您可以从函数中返回智能指针,并且析构函数 和 都将在其生存期结束时被调用。您也可以以这种方式创建。如果由于某种原因需要在智能指针仍处于活动状态时销毁对象,则可以或它。free()
std::shared_ptr
reset
release
评论
查明问题所在
首先,让我们通过说明每个语句后的内存状态来明确问题到底是什么。
int main() {
auto p = (Person*)malloc(sizeof(Person));
// +---+ +-------+
// | p | -> | ~~~~~ |
// +---+ +-------+
p = new(p)Person();
// +---+ +-------+
// | p | -> | name |
// +---+ +-------+
p->name=Test1::MSG1;
// +---+ +-------+ +---...
// | p | -> | name | -> |Something...
// +---+ +-------+ +---...
free(p);
// +---+ +---...
// | p | |Something...
// +---+ +---...
return 0;
}
如您所见,调用释放了最初由 分配的内存,但并没有释放分配到时分配的内存。free(p)
malloc
p->name
这是你的泄密。
解决问题
在堆上拥有对象有两个方面:Person
- 内存分配 - 由 / 此处处理。
malloc
free
- 初始化和完成该内存 - 通过调用构造函数和析构函数进行处理。
您缺少对析构函数的调用,因此 持有的资源被泄露。这里是记忆,但如果有一把锁,你可以有一个永远锁定的互斥锁,等等......因此,执行析构函数是必要的。Person
Person
C 风格的方法是自己调用析构函数:
int main() {
auto p = (Person*)malloc(sizeof(Person));
p = new(p) Person();
p->name = Test1::MSG1;
std::cout << "name: "<< p->name << "\n";
// Problem "fixed".
p->~Person();
free(p);
std::cout << "done" << "\n";
return 0;
}
然而,这不是惯用的 C++:它容易出错,等等......
C++ 方法是使用 RAII 来确保当超出范围时,其所有资源都得到正确处置:执行析构函数并释放为其自身分配的内存。p
Person
Person
首先,我们将创建一些帮助程序。我使用了命名空间,因为我不知道您使用的 C 库的名称,但我邀请您更具体:c
namespace c {
struct Disposer<T> {
void operator()(T* p) {
p->~T();
free(p);
}
};
template <typename T>
using UniquePointer<T> = std::unique_ptr<T, Disposer<T>>;
template <typename T, typename... Args>
UniquePointer<T> make_unique(T* t, Args&&... args) {
try {
new (t) T(std::forward<Args>(args)...);
} catch(...) {
free(t);
throw;
}
return UniquePointer{t};
}
} // namespace c
有了这个,我们可以改进原始示例:
int main() {
auto raw = (Person*) malloc(sizeof(Person));
auto p = c::make_unique(raw);
p->name = Test1::MSG1;
std::cout << "name: "<< p->name << "\n";
// No need to call the destructor or free ourselves, welcome to RAII.
std::cout << "done" << "\n";
return 0;
}
注意:不要使用 std::endl
,而是使用 '\n' 或 “\n”。
std::endl
在放置行尾的顶部调用 .flush(),
这很少是你想要的——它会减慢速度。
评论
free
malloc
free
#include <string>
#include <cstdlib>
<string>
<cstdlib>
<iostream>
alignas
std::malloc()