英文:
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)?
问题
这是我在实际代码中遇到的问题的最小工作示例。
#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
约束条件
- 在上面的示例中,我必须使用
malloc
,因为在我的实际代码中,我在我的C++项目中使用一个C库,该库在内部使用malloc
。因此,我不能摆脱对malloc
的使用,因为我在代码中没有显式使用它。 - 我需要在我的代码中一遍又一遍地重新分配
Person
的std::string name
。
英文:
This is a minimal working example for the problem I am facing in my real code.
#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;
}
When I compile it and run it via Valgrind, it gives me this error:
> definitely lost: 31 bytes in 1 blocks
Constraints
- I am bound to use
malloc
in the example above, as in my real code I use a C library in my C++ project, which uses thismalloc
internally. So I can't get away frommalloc
usage, as I don't do it explicitly anywhere in my code. - I need to reassign
std::string name
ofPerson
again and again in my code.
答案1
得分: 55
以下是代码的翻译部分:
为一个 Person 对象分配内存:
auto p = (Person*)malloc(sizeof(Person));
通过调用其构造函数,在已分配的内存中构造一个 Person 对象:
p = new(p)Person();
通过 malloc
分配的内存需要释放:
free(p);
通过放置 new
调用构造函数会创建一个 std::string
。该字符串将在析构函数中被销毁,但析构函数从未被调用。free
不会调用析构函数(就像 malloc
不会调用构造函数一样)。
malloc
仅分配内存。放置 new
只在已分配的内存中构造对象。因此,在调用 free
之前,您需要调用析构函数。这是我知道的唯一一种需要显式调用析构函数的情况:
auto p = (Person*)malloc(sizeof(Person));
p = new(p)Person();
p->~Person();
free(p);
英文:
The important pieces of your code line by line...
Allocate memory for one Person object:
auto p = (Person*)malloc(sizeof(Person));
Construct a Person object in that already allocated memory via calling its constructor:
p = new(p)Person();
Free the memory allocated via malloc:
free(p);
Calling the constructor via placement new
creates a std::string
. That string would be destroyed in the destructor but the destructor is never called. free
does not call destructors (just like malloc
does not call a constructor).
malloc
only allocates the memory. Placement new only constructs the object in already allocated memory. Hence you need to call the destructor before calling free
. This is the only case I am aware of where it is correct and necessary to explicitly call a destructor:
auto p = (Person*)malloc(sizeof(Person));
p = new(p)Person();
p->~Person();
free(p);
答案2
得分: 35
你必须在 free(p);
之前手动调用析构函数:
p->~Person();
或者使用 std::destroy_at(p)
,这是相同的操作。
英文:
You must manually call the destructor before free(p);
:
p->~Person();
Or std::destroy_at(p)
, which is the same thing.
答案3
得分: 31
问题定位
首先,让我们通过说明每个语句后内存的状态来明确问题所在。
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";
// 问题“已修复”。
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";
// 不需要手动调用析构函数或释放,欢迎使用 RAII。
std::cout << "done" << "\n";
return 0;
}
注意:不要使用 std::endl
,请改用 '\n'
或 "\\n"
。std::endl
除了添加换行符外还会调用 .flush()
,这通常不是你想要的行为——它会减慢程序。
英文:
Pinpointing the problem
First of all, let us be clear about exactly what the problem is by illustrating the state of the memory after each statement.
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;
}
As you can see, calling free(p)
freed up the memory originally allocated by malloc
, but it didn't free the memory allocated by p->name
when it was assigned to.
This is your leak.
Solving the problem
There are two aspects to having a Person
object on the heap:
- A memory allocation—handled by
malloc
/free
here. - Initializing and Finalizing that memory—handled by calls to constructors and destructors.
You're lacking the call to the destructor, hence resources held by Person
are leaked. Here it's memory, but if Person
held a lock you could have a forever locked mutex, etc... executing the destructor is therefore necessary.
The C-style approach is to call the destructor yourself:
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;
}
However this is not idiomatic C++: it's error prone, etc...
The C++ approach is to use RAII to ensure that when p
goes out of scope, all its resources are properly disposed of: the destructor of Person
is executed and the memory allocated for Person
itself is freed.
First of all, we're going to create some helpers. I used the c
namespace since I don't know the name of the C library you use, but I invite you to be more specific:
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
And with that, we can improve the original example:
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;
}
Note: Do not use std::endl
, use '\n'
or "\n"
instead. std::endl
calls .flush()
on top of putting an end of line, which is rarely what you want -- it slows things down.
答案4
得分: 11
作为在其他回答中提到的,泄漏的源头在于Person
的name
成员的析构函数没有被调用。在正常情况下,当Person
的析构函数被调用时,name
的析构函数会隐式地被调用。然而,Person
从未被销毁。Person
实例的内存只是使用free
函数释放。
因此,就像你不得不在使用malloc
后显式调用构造函数一样,你还需要在使用free
之前显式调用析构函数。
你还可以考虑重载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指针会自动删除
}
当然,你也可以在使用malloc
和free
的情况下使用带有自定义删除器的unique_ptr
,但这将变得更加繁琐,而且你的删除器仍然需要知道显式调用析构函数。
英文:
As mentioned in other answers, the source of the leak is that the destructor for the name
member of Person
does not get called. It would normally be called implicitly when the destructor for Person
is called. However, Person
is never destructed. The memory for the Person
instance is simply released with free
.
So, just as you had to explicitly invoke the constructor with the placement new
after malloc
, you also need to explicitly invoke the destructor before free
.
You can also consider overloading the new
and delete
operators.
struct Person {
std::string name;
void * operator new (std::size_t sz) { return std::malloc(sz); }
void operator delete (void *p) { std::free(p); }
};
This way, you can use new
and delete
normally, when underneath they will use malloc
and free
.
int main (void) {
auto p = new Person;
//...
delete p;
}
And this way, you can more naturally use a smart pointer.
int main (void) {
auto p = std:make_unique<Person>();
//... unique pointer will delete automatically
}
Of course, you could have used unique_ptr
with a custom deleter with your explicit calls to malloc
and free
, but it would have been much more cumbersome, and your deleter would still need to know to explicitly invoke the destructor as well.
答案5
得分: 6
如其他人所提到的,由 Person
成员分配的动态内存只有在析构函数 ~Person
中被释放,free()
不会调用它。
如果你必须在需要一些初始化和清理工作的库中使用这个函数,而不仅仅是默认的情况,比如在这里,一个方法是定义一个新的删除器,供标准库智能指针使用:这将适用于你自己没有分配的内存块。
#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() // 外部代码的虚构函数。
{
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
。
英文:
As others have mentioned, dynamic memory allocated by the members of Person
only gets freed by the destructor ~Person
, which free()
does not call.
If you have to use this function with a library that requires some initialization and clean-up other than the default, such as here, one approach is to define a new deleter, for the standard libray smart pointers to use: This will work even with a block of memory you did not allocate yourself.
#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;
}
This lets you safely use:
const auto p =
std::unique_ptr<Person, PersonDeleterForSomeLib>(Person_factory());
with automatic memory management. You can return the smart pointer from the function, and both the destructor and free()
will be called when its lifetime ends. You can also create a std::shared_ptr
this way. If for some reason you need to destroy the object while the smart pointer is still alive, you can reset
or release
it.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论