Anu*_*hra 58 c++ valgrind memory-leaks placement-new dynamic-memory-allocation
这是我在实际代码中遇到的问题的最小工作示例。
#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;
}
Run Code Online (Sandbox Code Playgroud)
当我编译它并通过Valgrind运行它时,它给了我这个错误:
肯定丢失:1 个块中 31 个字节
malloc
在上面的示例中使用,因为在我的实际代码中,我在 C++ 项目中使用了 C 库,该项目malloc
在内部使用了它。所以我无法摆脱malloc
使用,因为我没有在代码中的任何地方明确地这样做。std::string name
分配。Person
for*_*818 55
代码的重要部分逐行......
为一个 Person 对象分配内存:
auto p = (Person*)malloc(sizeof(Person));
Run Code Online (Sandbox Code Playgroud)
通过调用其构造函数在已分配的内存中构造一个 Person 对象:
p = new(p)Person();
Run Code Online (Sandbox Code Playgroud)
释放通过 malloc 分配的内存:
free(p);
Run Code Online (Sandbox Code Playgroud)
通过放置调用构造函数new
会创建一个std::string
. 该字符串将在析构函数中被销毁,但析构函数永远不会被调用。free
不调用析构函数(就像malloc
不调用构造函数一样)。
malloc
只分配内存。放置 new 仅在已分配的内存中构造对象。因此,您需要在调用之前调用析构函数free
。这是我所知道的唯一正确且有必要显式调用析构函数的情况:
auto p = (Person*)malloc(sizeof(Person));
p = new(p)Person();
p->~Person();
free(p);
Run Code Online (Sandbox Code Playgroud)
Hol*_*Cat 35
您必须在此之前手动调用析构函数free(p);
:
p->~Person();
Run Code Online (Sandbox Code Playgroud)
或者std::destroy_at(p)
,这是同一件事。
Mat*_* M. 31
首先,让我们通过说明每个语句后的内存状态来明确问题所在。
\nint main() {\n auto p = (Person*)malloc(sizeof(Person));\n\n // +---+ +-------+\n // | p | -> | ~~~~~ |\n // +---+ +-------+\n\n p = new(p)Person();\n\n // +---+ +-------+\n // | p | -> | name |\n // +---+ +-------+\n\n p->name=Test1::MSG1;\n\n // +---+ +-------+ +---...\n // | p | -> | name | -> |Something...\n // +---+ +-------+ +---...\n\n free(p);\n\n // +---+ +---...\n // | p | |Something...\n // +---+ +---...\n\n return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n正如您所看到的,调用free(p)
释放了最初由 分配的内存malloc
,但它没有释放p->name
分配时由 分配的内存。
这是你的泄漏。
\nPerson
将对象放在堆上有两个方面:
malloc
这里由/处理的内存分配\xe2\x80\x94 free
。您缺少对析构函数的调用,因此所持有的资源Person
被泄漏。这里是内存,但如果Person
持有锁,您可能会拥有永久锁定的互斥体等......因此有必要执行析构函数。
C 风格的方法是自己调用析构函数:
\nint main() {\n auto p = (Person*)malloc(sizeof(Person));\n p = new(p) Person();\n p->name = Test1::MSG1;\n\n std::cout << "name: "<< p->name << "\\n";\n\n // Problem "fixed".\n p->~Person();\n\n free(p);\n\n std::cout << "done" << "\\n";\n\n return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n然而,这不是惯用的 C++:它容易出错,等等......
\nC++ 的方法是使用RAII来确保当p
超出范围时,其所有资源都得到正确处置:Person
执行析构函数并释放为其自身分配的内存Person
。
首先,我们将创建一些助手。我使用了c
命名空间,因为我不知道您使用的 C 库的名称,但我邀请您更具体:
namespace c {\nstruct Disposer<T> {\n void operator()(T* p) {\n p->~T();\n free(p);\n }\n};\n\ntemplate <typename T>\nusing UniquePointer<T> = std::unique_ptr<T, Disposer<T>>;\n\ntemplate <typename T, typename... Args>\nUniquePointer<T> make_unique(T* t, Args&&... args) {\n try {\n new (t) T(std::forward<Args>(args)...);\n } catch(...) {\n free(t);\n throw;\n }\n\n return UniquePointer{t};\n}\n} // namespace c\n
Run Code Online (Sandbox Code Playgroud)\n这样,我们就可以改进原来的例子:
\nint main() {\n auto raw = (Person*) malloc(sizeof(Person));\n\n auto p = c::make_unique(raw);\n\n p->name = Test1::MSG1;\n\n std::cout << "name: "<< p->name << "\\n";\n\n // No need to call the destructor or free ourselves, welcome to RAII.\n\n std::cout << "done" << "\\n";\n\n return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n注意:请勿使用std::endl
、使用\'\\n\'
或"\\n"
代替。在结束线路之上std::endl
调用,这很少是您想要的——它会减慢速度。.flush()
jxh*_*jxh 11
正如其他答案中提到的,泄漏的根源是成员的析构函数name
没有Person
被调用。当调用 for 的析构函数时,它通常会被隐式调用Person
。然而,Person
永远不会被破坏。实例的内存Person
只需使用 即可释放free
。
因此,正如您必须显式调用放置在new
after 的构造函数一样malloc
,您也需要显式调用 before 的析构函数free
。
您还可以考虑重载new
anddelete
运算符。
struct Person {
std::string name;
void * operator new (std::size_t sz) { return std::malloc(sz); }
void operator delete (void *p) { std::free(p); }
};
Run Code Online (Sandbox Code Playgroud)
这样,您可以正常使用new
and delete
,当在下面时它们将使用malloc
and free
。
int main (void) {
auto p = new Person;
//...
delete p;
}
Run Code Online (Sandbox Code Playgroud)
这样,您就可以更自然地使用智能指针。
int main (void) {
auto p = std:make_unique<Person>();
//... unique pointer will delete automatically
}
Run Code Online (Sandbox Code Playgroud)
当然,您可以unique_ptr
通过显式调用 和 来使用自定义删除器malloc
,free
但这会更加麻烦,并且您的删除器仍然需要知道显式调用析构函数。
正如其他人提到的,由成员分配的动态内存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() // 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;
}
Run Code Online (Sandbox Code Playgroud)
这可以让您安全地使用:
const auto p =
std::unique_ptr<Person, PersonDeleterForSomeLib>(Person_factory());
Run Code Online (Sandbox Code Playgroud)
具有自动内存管理功能。您可以从函数返回智能指针,并且析构函数和析构函数都free()
将在其生命周期结束时被调用。您也可以通过这种方式创建std::shared_ptr
。如果由于某种原因您需要在智能指针仍然存在的情况下销毁该对象,您可以reset
或release
它。
归档时间: |
|
查看次数: |
5415 次 |
最近记录: |