避免 C++ 中内存泄漏的一般准则

发布于 2024-07-04 04:26:59 字数 51 浏览 8 评论 0原文

确保 C++ 程序中不会泄漏内存的一般技巧有哪些? 如何确定谁应该释放动态分配的内存?

What are some general tips to make sure I don't leak memory in C++ programs? How do I figure out who should free memory that has been dynamically allocated?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(29

别理我 2024-07-11 04:27:00

按重要性排序的提示:

-提示#1 始终记住将析构函数声明为“虚拟”。

-Tip#2 使用 RAII

-Tip#3 使用 boost 的智能指针

-Tip#4 不要编写自己有缺陷的智能指针,使用 boost(在我现在正在进行的一个项目中,我不能使用 boost,而且我已经受过苦了)必须调试我自己的智能指针,我肯定不会再走同样的路线,但现在我又无法为我们的依赖项添加提升)

-Tip#5 如果它是一些休闲/非性能关键的(如在游戏中)包含数千个对象)请查看 Thorsten Ottosen 的 boost 指针容器

-Tip#6 为您选择的平台找到泄漏检测标头,例如 Visual Leak Inspection 的“vld”标头

Tips in order of Importance:

-Tip#1 Always remember to declare your destructors "virtual".

-Tip#2 Use RAII

-Tip#3 Use boost's smartpointers

-Tip#4 Don't write your own buggy Smartpointers, use boost (on a project I'm on right now I can't use boost, and I've suffered having to debug my own smart pointers, I would definately not take the same route again, but then again right now I can't add boost to our dependencies)

-Tip#5 If its some casual/non-performance critical (as in games with thousands of objects) work look at Thorsten Ottosen's boost pointer container

-Tip#6 Find a leak detection header for your platform of choice such as Visual Leak Detection's "vld" header

萧瑟寒风 2024-07-11 04:27:00

如果可以,请使用 boost shared_ptr 和标准 C++ auto_ptr。 这些传达了所有权语义。

当您返回 auto_ptr 时,您是在告诉调用者您正在授予他们内存的所有权。

当您返回一个shared_ptr时,您就告诉调用者您拥有对它的引用,并且他们拥有部分所有权,但这不仅仅是他们的责任。

这些语义也适用于参数。 如果调用者传递给你一个 auto_ptr,他们就给你所有权。

If you can, use boost shared_ptr and standard C++ auto_ptr. Those convey ownership semantics.

When you return an auto_ptr, you are telling the caller that you are giving them ownership of the memory.

When you return a shared_ptr, you are telling the caller that you have a reference to it and they take part of the ownership, but it isn't solely their responsibility.

These semantics also apply to parameters. If the caller passes you an auto_ptr, they are giving you ownership.

固执像三岁 2024-07-11 04:27:00

其他人首先提到了避免内存泄漏的方法(例如智能指针)。 但是,一旦出现内存问题,分析和内存分析工具通常是追踪内存问题的唯一方法。

Valgrind memcheck 是一款出色的免费工具。

Others have mentioned ways of avoiding memory leaks in the first place (like smart pointers). But a profiling and memory-analysis tool is often the only way to track down memory problems once you have them.

Valgrind memcheck is an excellent free one.

奢华的一滴泪 2024-07-11 04:27:00

仅对于 MSVC,将以下内容添加到每个 .cpp 文件的顶部:

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

然后,在使用 VS2003 或更高版本进行调试时,当程序退出时(它跟踪新的/删除的),您将被告知任何泄漏。 这是基本的,但它过去对我有帮助。

For MSVC only, add the following to the top of each .cpp file:

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

Then, when debugging with VS2003 or greater, you will be told of any leaks when your program exits (it tracks new/delete). It's basic, but it has helped me in the past.

っ左 2024-07-11 04:27:00

这些错误的常见来源是当您有一个方法接受对象的引用或指针但所有权不明确时。 风格和评论惯例可以降低这种可能性。

让函数获得对象所有权的情况作为特例。 在发生这种情况的所有情况下,请务必在头文件中的函数旁边编写注释来表明这一点。 您应该努力确保在大多数情况下分配对象的模块或类也负责释放它。

在某些情况下使用 const 会有很大帮助。 如果函数不会修改对象,并且不会存储返回后仍保留的对该对象的引用,则接受 const 引用。 通过阅读调用者的代码,很明显您的函数尚未接受该对象的所有权。 您可以让相同的函数接受非常量指针,并且调用者可能会也可能不会假设被调用者接受所有权,但是对于 const 引用,这是没有问题的。

不要在参数列表中使用非常量引用。 在阅读调用者代码时,非常不清楚被调用者可能保留了对参数的引用。

我不同意推荐引用计数指针的评论。 这通常工作得很好,但是当你有一个错误并且它不起作用时,特别是如果你的析构函数做了一些不平凡的事情,例如在多线程程序中。 如果不是太难的话,一定要尝试调整您的设计,使其不需要引用计数。

A frequent source of these bugs is when you have a method that accepts a reference or pointer to an object but leaves ownership unclear. Style and commenting conventions can make this less likely.

Let the case where the function takes ownership of the object be the special case. In all situations where this happens, be sure to write a comment next to the function in the header file indicating this. You should strive to make sure that in most cases the module or class which allocates an object is also responsible for deallocating it.

Using const can help a lot in some cases. If a function will not modify an object, and does not store a reference to it that persists after it returns, accept a const reference. From reading the caller's code it will be obvious that your function has not accepted ownership of the object. You could have had the same function accept a non-const pointer, and the caller may or may not have assumed that the callee accepted ownership, but with a const reference there's no question.

Do not use non-const references in argument lists. It is very unclear when reading the caller code that the callee may have kept a reference to the parameter.

I disagree with the comments recommending reference counted pointers. This usually works fine, but when you have a bug and it doesn't work, especially if your destructor does something non-trivial, such as in a multithreaded program. Definitely try to adjust your design to not need reference counting if it's not too hard.

Spring初心 2024-07-11 04:27:00

valgrind(仅适用于 *nix 平台)是一个非常好的内存检查器

valgrind (only avail for *nix platforms) is a very nice memory checker

旧街凉风 2024-07-11 04:27:00
  • 尽量避免动态分配对象。 只要类具有适当的构造函数和析构函数,请使用类类型的变量,而不是指向它的指针,并且您可以避免动态分配和释放,因为编译器会为您执行此操作。
    实际上,这也是“智能指针”使用的机制,并被其他一些作者称为 RAII;-)。
  • 当您将对象传递给其他函数时,优先选择引用参数而不是指针。 这可以避免一些可能的错误。
  • 尽可能将参数声明为 const,尤其是指向对象的指针。 这样,对象就不会被“意外”释放(除非你放弃了 const ;-)))。
  • 尽量减少程序中进行内存分配和释放的位置数量。 例如 如果您多次分配或释放同一类型,请为其编写一个函数(或工厂方法;-))。
    通过这种方式,如果需要,您可以轻松创建调试输出(分配和释放地址......)。
  • 使用工厂函数从单个函数分配多个相关类的对象。
  • 如果您的类有一个带有虚拟析构函数的公共基类,您可以使用相同的函数(或静态方法)释放所有它们。
  • 使用 purify 等工具检查您的程序(不幸的是很多美元/欧元/...)。
  • Try to avoid allocating objects dynamically. As long as classes have appropriate constructors and destructors, use a variable of the class type, not a pointer to it, and you avoid dynamical allocation and deallocation because the compiler will do it for you.
    Actually that's also the mechanism used by "smart pointers" and referred to as RAII by some of the other writers ;-) .

  • When you pass objects to other functions, prefer reference parameters over pointers. This avoids some possible errors.
  • Declare parameters const, where possible, especially pointers to objects. That way objects can't be freed "accidentially" (except if you cast the const away ;-))).
  • Minimize the number of places in the program where you do memory allocation and deallocation. E. g. if you do allocate or free the same type several times, write a function for it (or a factory method ;-)).
    This way you can create debug output (which addresses are allocated and deallocated, ...) easily, if required.

  • Use a factory function to allocate objects of several related classes from a single function.
  • If your classes have a common base class with a virtual destructor, you can free all of them using the same function (or static method).
  • Check your program with tools like purify (unfortunately many $/€/...).
装迷糊 2024-07-11 04:27:00

您可以拦截内存分配函数并查看是否有一些内存区域在程序退出时未释放(尽管它并不适合所有应用程序)。

也可以在编译时通过替换operator new和delete等内存分配函数来完成。

例如,检查此 站点 [调试内存分配C++]
注意:删除运算符还有一个类似这样的技巧:

#define DEBUG_DELETE PrepareDelete(__LINE__,__FILE__); delete
#define delete DEBUG_DELETE

您可以在某些变量中存储文件名,并且重载的删除运算符何时会知道它是从哪个位置调用的。 这样您就可以跟踪程序中的每次删除和分配。 在内存检查序列结束时,您应该能够报告分配的内存块没有被“删除”,通过文件名和行号来识别它,我猜这就是您想要的。

您还可以在 Visual Studio 下尝试类似 BoundsChecker 的东西,这非常有趣且简单使用。

You can intercept the memory allocation functions and see if there are some memory zones not freed upon program exit (though it is not suitable for all the applications).

It can also be done at compile time by replacing operators new and delete and other memory allocation functions.

For example check in this site [Debugging memory allocation in C++]
Note: There is a trick for delete operator also something like this:

#define DEBUG_DELETE PrepareDelete(__LINE__,__FILE__); delete
#define delete DEBUG_DELETE

You can store in some variables the name of the file and when the overloaded delete operator will know which was the place it was called from. This way you can have the trace of every delete and malloc from your program. At the end of the memory checking sequence you should be able to report what allocated block of memory was not 'deleted' identifying it by filename and line number which is I guess what you want.

You could also try something like BoundsChecker under Visual Studio which is pretty interesting and easy to use.

ゞ记忆︶ㄣ 2024-07-11 04:27:00

我们用一个层包装所有的分配函数,该层在前面附加一个简短的字符串,在末尾附加一个哨兵标志。 例如,您可以调用“myalloc( pszSomeString, iSize, iAlignment ); 或 new( "description", iSize ) MyObject(); ,它会在内部分配指定的大小以及足够的空间供标头和哨兵使用。当然,不要忘记在非调试版本中注释掉这一点!这需要更多的内存,但好处远远超过成本,

这有三个好处 - 首先,它允许您轻松快速地跟踪泄漏的代码。 ,通过快速搜索分配在某些“区域”中但在这些区域应该释放时未被清理的代码,通过检查确保所有哨兵完好无损来检测边界何时被覆盖也很有用。第三个好处是跟踪内存的使用情况,以了解谁是主要参与者 - MemDump 中某些描述的整理会告诉您“声音”何时占用。例如,空间比您预期的要大得多。

We wrap all our allocation functions with a layer that appends a brief string at the front and a sentinel flag at the end. So for example you'd have a call to "myalloc( pszSomeString, iSize, iAlignment ); or new( "description", iSize ) MyObject(); which internally allocates the specified size plus enough space for your header and sentinel. Of course, don't forget to comment this out for non-debug builds! It takes a little more memory to do this but the benefits far outweigh the costs.

This has three benefits - first it allows you to easily and quickly track what code is leaking, by doing quick searches for code allocated in certain 'zones' but not cleaned up when those zones should have freed. It can also be useful to detect when a boundary has been overwritten by checking to ensure all sentinels are intact. This has saved us numerous times when trying to find those well-hidden crashes or array missteps. The third benefit is in tracking the use of memory to see who the big players are - a collation of certain descriptions in a MemDump tells you when 'sound' is taking up way more space than you anticipated, for example.

陪我终i 2024-07-11 04:27:00

C++ 的设计考虑了 RAII。 我认为在 C++ 中确实没有更好的内存管理方法。
但要小心,不要在本地范围内分配非常大的块(如缓冲区对象)。 它可能会导致堆栈溢出,并且如果在使用该块时边界检查存在缺陷,则可以覆盖其他变量或返回地址,这会导致各种安全漏洞。

C++ is designed RAII in mind. There is really no better way to manage memory in C++ I think.
But be careful not to allocate very big chunks (like buffer objects) on local scope. It can cause stack overflows and, if there is a flaw in bounds checking while using that chunk, you can overwrite other variables or return addresses, which leads to all kinds security holes.

戴着白色围巾的女孩 2024-07-11 04:27:00

关于在不同位置分配和销毁的唯一示例之一是线程创建(您传递的参数)。
但即使在这种情况下也很容易。
这是创建线程的函数/方法:

struct myparams {
int x;
std::vector<double> z;
}

std::auto_ptr<myparams> param(new myparams(x, ...));
// Release the ownership in case thread creation is successfull
if (0 == pthread_create(&th, NULL, th_func, param.get()) param.release();
...

这里是线程函数

extern "C" void* th_func(void* p) {
   try {
       std::auto_ptr<myparams> param((myparams*)p);
       ...
   } catch(...) {
   }
   return 0;
}

很简单不是吗? 如果线程创建失败,资源将被 auto_ptr 释放(删除),否则所有权将传递给线程。
如果线程速度太快以至于在创建后

param.release();

在主函数/方法中调用之前释放了资源怎么办? 没有什么! 因为我们会“告诉”auto_ptr 忽略释放。
C++ 内存管理是不是很简单?
干杯,

艾玛!

One of the only examples about allocating and destroying in different places is thread creation (the parameter you pass).
But even in this case is easy.
Here is the function/method creating a thread:

struct myparams {
int x;
std::vector<double> z;
}

std::auto_ptr<myparams> param(new myparams(x, ...));
// Release the ownership in case thread creation is successfull
if (0 == pthread_create(&th, NULL, th_func, param.get()) param.release();
...

Here instead the thread function

extern "C" void* th_func(void* p) {
   try {
       std::auto_ptr<myparams> param((myparams*)p);
       ...
   } catch(...) {
   }
   return 0;
}

Pretty easyn isn't it? In case the thread creation fails the resource will be free'd (deleted) by the auto_ptr, otherwise the ownership will be passed to the thread.
What if the thread is so fast that after creation it releases the resource before the

param.release();

gets called in the main function/method? Nothing! Because we will 'tell' the auto_ptr to ignore the deallocation.
Is C++ memory management easy isn't it?
Cheers,

Ema!

暮色兮凉城 2024-07-11 04:27:00

如果您要手动管理内存,则有两种情况:

  1. 我创建了对象(可能间接地,通过调用分配新对象的函数),我使用它(或者我调用的函数使用它),然后我释放它。
  2. 有人给了我参考,所以我不应该释放它。

如果您需要违反任何这些规则,请记录下来。

这都是关于指针所有权的。

If you are going to manage your memory manually, you have two cases:

  1. I created the object (perhaps indirectly, by calling a function that allocates a new object), I use it (or a function I call uses it), then I free it.
  2. Somebody gave me the reference, so I should not free it.

If you need to break any of these rules, please document it.

It is all about pointer ownership.

落在眉间の轻吻 2024-07-11 04:27:00

任何函数的恰好一个返回。 这样你就可以在那里进行释放并且永远不会错过它。

否则很容易犯错误:

new a()
if (Bad()) {delete a; return;}
new b()
if (Bad()) {delete a; delete b; return;}
... // etc.

Exactly one return from any function. That way you can do deallocation there and never miss it.

It's too easy to make a mistake otherwise:

new a()
if (Bad()) {delete a; return;}
new b()
if (Bad()) {delete a; delete b; return;}
... // etc.
一身仙ぐ女味 2024-07-11 04:27:00

管理内存的方式与管理其他资源(句柄、文件、数据库连接、套接字...)相同。 GC 也不会帮助你。

Manage memory the same way you manage other resources (handles, files, db connections, sockets...). GC would not help you with them either.

思念满溢 2024-07-11 04:26:59

我完全赞同有关 RAII 和智能指针的所有建议,但我还想添加一个稍微更高级别的提示:最容易管理的内存是您从未分配的内存。 与 C# 和 Java 等几乎所有内容都是引用的语言不同,在 C++ 中,您应该尽可能将对象放入堆栈中。 正如我看到一些人(包括 Stroustrup 博士)指出的那样,垃圾收集在 C++ 中从未流行的主要原因是编写良好的 C++ 首先不会产生太多垃圾。

不要写

Object* x = new Object;

,甚至

shared_ptr<Object> x(new Object);

当你可以写的时候

Object x;

I thoroughly endorse all the advice about RAII and smart pointers, but I'd also like to add a slightly higher-level tip: the easiest memory to manage is the memory you never allocated. Unlike languages like C# and Java, where pretty much everything is a reference, in C++ you should put objects on the stack whenever you can. As I've see several people (including Dr Stroustrup) point out, the main reason why garbage collection has never been popular in C++ is that well-written C++ doesn't produce much garbage in the first place.

Don't write

Object* x = new Object;

or even

shared_ptr<Object> x(new Object);

when you can just write

Object x;
四叶草在未来唯美盛开 2024-07-11 04:26:59

使用RAII

  • 忘记垃圾收集(改为使用 RAII)。 请注意,即使垃圾收集器也可能泄漏(如果您忘记在 Java/C# 中“清空”某些引用),并且垃圾收集器不会帮助您处置资源(如果您有一个对象获取了一个文件,当对象超出范围时,如果您不在 Java 中手动执行此操作,或在 C# 中使用“dispose”模式,则该文件不会自动释放。
  • 忘记“每个函数一次返回”规则。 这是避免泄漏的一个很好的 C 建议,但它在 C++ 中已经过时了,因为它使用了异常(改为使用 RAII)。
  • 虽然“三明治模式”是一个很好的 C 建议,但它在 C++ 中已经过时了,因为它使用了异常(改为使用 RAII)。

这篇文章似乎是重复的,但在 C++ 中,需要了解的最基本模式是 RAII

学习使用智能指针,无论是 boost、TR1 还是低级(但通常足够高效)的 auto_ptr(但你必须知道它的局限性)。

RAII 是 C++ 中异常安全和资源处置的基础,没有其他模式(三明治等)可以同时满足您的要求(而且大多数时候,它不会给您提供任何帮助)。

请参阅下面 RAII 和非 RAII 代码的比较:

void doSandwich()
{
   T * p = new T() ;
   // do something with p
   delete p ; // leak if the p processing throws or return
}

void doRAIIDynamic()
{
   std::auto_ptr<T> p(new T()) ; // you can use other smart pointers, too
   // do something with p
   // WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}

void doRAIIStatic()
{
   T p ;
   // do something with p
   // WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}

关于 RAII

总结(在 < i>Ogre Psalm33),RAII 依赖于三个概念:

  • 一旦对象被构造,它就可以工作! 在构造函数中获取资源。
  • 对象销毁就足够了!在析构函数中释放资源。
  • 一切都与作用域有关!作用域对象(参见上面的 doRAIIStatic 示例)将在声明时构造,并在执行退出作用域时被销毁,无论退出方式如何(返回、中断、异常) , ETC。)。

这意味着在正确的 C++ 代码中,大多数对象不会使用 new 构造,而是在堆栈上声明。 对于那些使用new构造的,所有内容都将以某种方式范围(例如附加到智能指针)。

作为开发人员,这确实非常强大,因为您不需要关心手动资源处理(如 C 中所做的那样,或者对于 Java 中的某些对象来说,它们大量使用 try/最后对于这种情况)...

编辑(2012-02-12)

“作用域对象......将被破坏......无论退出”这并不完全正确。 有很多方法可以欺骗 RAII。 任何形式的终止()都会绕过清理。 exit(EXIT_SUCCESS) 在这方面是一个矛盾的说法。

威廉特尔

威廉特尔 > 对此非常正确:有一些特殊方法可以欺骗 RAII,所有这些都会导致进程突然停止。

这些都是例外方式,因为 C++ 代码中并不充斥着终止、退出等,或者在有例外的情况下,我们确实需要一个 未处理的异常使进程崩溃并按原样核心转储其内存映像,而不是在清理后。

但我们仍然必须了解这些案例,因为虽然它们很少发生,但仍然可能发生。

(谁在随意的 C++ 代码中调用 terminateexit ?...我记得在使用 GLUT:这个库非常面向 C,甚至积极地设计它,让 C++ 开发人员的事情变得困难。关心堆栈分配的数据,或者做出“有趣”的决定永远不会从他们的主循环中返回...我不会对此发表评论)

Use RAII

  • Forget Garbage Collection (Use RAII instead). Note that even the Garbage Collector can leak, too (if you forget to "null" some references in Java/C#), and that Garbage Collector won't help you to dispose of resources (if you have an object which acquired a handle to a file, the file won't be freed automatically when the object will go out of scope if you don't do it manually in Java, or use the "dispose" pattern in C#).
  • Forget the "one return per function" rule. This is a good C advice to avoid leaks, but it is outdated in C++ because of its use of exceptions (use RAII instead).
  • And while the "Sandwich Pattern" is a good C advice, it is outdated in C++ because of its use of exceptions (use RAII instead).

This post seem to be repetitive, but in C++, the most basic pattern to know is RAII.

Learn to use smart pointers, both from boost, TR1 or even the lowly (but often efficient enough) auto_ptr (but you must know its limitations).

RAII is the basis of both exception safety and resource disposal in C++, and no other pattern (sandwich, etc.) will give you both (and most of the time, it will give you none).

See below a comparison of RAII and non RAII code:

void doSandwich()
{
   T * p = new T() ;
   // do something with p
   delete p ; // leak if the p processing throws or return
}

void doRAIIDynamic()
{
   std::auto_ptr<T> p(new T()) ; // you can use other smart pointers, too
   // do something with p
   // WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}

void doRAIIStatic()
{
   T p ;
   // do something with p
   // WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}

About RAII

To summarize (after the comment from Ogre Psalm33), RAII relies on three concepts:

  • Once the object is constructed, it just works! Do acquire resources in the constructor.
  • Object destruction is enough! Do free resources in the destructor.
  • It's all about scopes! Scoped objects (see doRAIIStatic example above) will be constructed at their declaration, and will be destroyed the moment the execution exits the scope, no matter how the exit (return, break, exception, etc.).

This means that in correct C++ code, most objects won't be constructed with new, and will be declared on the stack instead. And for those constructed using new, all will be somehow scoped (e.g. attached to a smart pointer).

As a developer, this is very powerful indeed as you won't need to care about manual resource handling (as done in C, or for some objects in Java which makes intensive use of try/finally for that case)...

Edit (2012-02-12)

"scoped objects ... will be destructed ... no matter the exit" that's not entirely true. there are ways to cheat RAII. any flavour of terminate() will bypass cleanup. exit(EXIT_SUCCESS) is an oxymoron in this regard.

wilhelmtell

wilhelmtell is quite right about that: There are exceptional ways to cheat RAII, all leading to the process abrupt stop.

Those are exceptional ways because C++ code is not littered with terminate, exit, etc., or in the case with exceptions, we do want an unhandled exception to crash the process and core dump its memory image as is, and not after cleaning.

But we must still know about those cases because, while they rarely happen, they can still happen.

(who calls terminate or exit in casual C++ code?... I remember having to deal with that problem when playing with GLUT: This library is very C-oriented, going as far as actively designing it to make things difficult for C++ developers like not caring about stack allocated data, or having "interesting" decisions about never returning from their main loop... I won't comment about that).

风筝有风,海豚有海 2024-07-11 04:26:59

阅读 RAII 并确保您理解它。

Read up on RAII and make sure you understand it.

渔村楼浪 2024-07-11 04:26:59

呸,你们这些小孩子和你们新奇的垃圾收集器……

关于“所有权”的非常严格的规则——什么对象或软件的一部分有权删除该对象。 清晰的注释和明智的变量名称可以使指针“拥有”或“只看,不碰”变得显而易见。 为了帮助确定谁拥有什么,请在每个子例程或方法中尽可能遵循“三明治”模式。

create a thing
use that thing
destroy that thing

有时需要在不同的地方进行创造和破坏; 我认为很难避免这种情况。

在任何需要复杂数据结构的程序中,我使用“所有者”指针创建一个严格清晰的包含其他对象的对象树。 该树对应用程序域概念的基本层次结构进行了建模。 例如,3D 场景拥有对象、灯光、纹理。 在渲染结束时,当程序退出时,有一个明确的方法可以销毁所有内容。

每当一个实体需要访问另一个实体、扫描数组或其他任何东西时,都会根据需要定义许多其他指针; 这些都是“只是看看”。 对于 3D 场景示例 - 对象使用纹理但不拥有; 其他对象可能会使用相同的纹理。 对象的销毁不会导致任何纹理的销毁。

是的,这很耗时,但这就是我所做的。 我很少遇到内存泄漏或其他问题。 但后来我在高性能科学、数据采集和图形软件的有限领域工作。 我不经常处理银行和电子商务、事件驱动的 GUI 或高度网络异步混乱等事务。 也许新奇的方法在那里有一个优势!

Bah, you young kids and your new-fangled garbage collectors...

Very strong rules on "ownership" - what object or part of the software has the right to delete the object. Clear comments and wise variable names to make it obvious if a pointer "owns" or is "just look, don't touch". To help decide who owns what, follow as much as possible the "sandwich" pattern within every subroutine or method.

create a thing
use that thing
destroy that thing

Sometimes it's necessary to create and destroy in widely different places; i think hard to avoid that.

In any program requiring complex data structures, i create a strict clear-cut tree of objects containing other objects - using "owner" pointers. This tree models the basic hierarchy of application domain concepts. Example a 3D scene owns objects, lights, textures. At the end of the rendering when the program quits, there's a clear way to destroy everything.

Many other pointers are defined as needed whenever one entity needs access another, to scan over arays or whatever; these are the "just looking". For the 3D scene example - an object uses a texture but does not own; other objects may use that same texture. The destruction of an object does not invoke destruction of any textures.

Yes it's time consuming but that's what i do. I rarely have memory leaks or other problems. But then i work in the limited arena of high-performance scientific, data acquisition and graphics software. I don't often deal transactions like in banking and ecommerce, event-driven GUIs or high networked asynchronous chaos. Maybe the new-fangled ways have an advantage there!

揽月 2024-07-11 04:26:59

您需要查看智能指针,例如 boost 的智能指针

一旦

int main()
{ 
    Object* obj = new Object();
    //...
    delete obj;
}

引用计数为零,boost::shared_ptr 就会自动删除:

int main()
{
    boost::shared_ptr<Object> obj(new Object());
    //...
    // destructor destroys when reference count is zero
}

请注意我的最后一条注释,“当引用计数为零时,这是最酷的部分。因此,如果您的对象有多个用户,则不必这样做跟踪该对象是否仍在使用。一旦没有人引用您的共享指针,它就会被销毁。

尽管您可以访问基指针,但您不想将其传递给第三个。除非您对它正在执行的操作充满信心,否则很多时候,您会在创建范围完成后将内容“发布”到其他线程以完成工作,这在 Win32 中的 PostThreadMessage 中很常见:

void foo()
{
   boost::shared_ptr<Object> obj(new Object()); 

   // Simplified here
   PostThreadMessage(...., (LPARAM)ob.get());
   // Destructor destroys! pointer sent to PostThreadMessage is invalid! Zohnoes!
}

一如既往,使用您的 API。用任何工具思考帽...

You'll want to look at smart pointers, such as boost's smart pointers.

Instead of

int main()
{ 
    Object* obj = new Object();
    //...
    delete obj;
}

boost::shared_ptr will automatically delete once the reference count is zero:

int main()
{
    boost::shared_ptr<Object> obj(new Object());
    //...
    // destructor destroys when reference count is zero
}

Note my last note, "when reference count is zero, which is the coolest part. So If you have multiple users of your object, you won't have to keep track of whether the object is still in use. Once nobody refers to your shared pointer, it gets destroyed.

This is not a panacea, however. Though you can access the base pointer, you wouldn't want to pass it to a 3rd party API unless you were confident with what it was doing. Lots of times, your "posting" stuff to some other thread for work to be done AFTER the creating scope is finished. This is common with PostThreadMessage in Win32:

void foo()
{
   boost::shared_ptr<Object> obj(new Object()); 

   // Simplified here
   PostThreadMessage(...., (LPARAM)ob.get());
   // Destructor destroys! pointer sent to PostThreadMessage is invalid! Zohnoes!
}

As always, use your thinking cap with any tool...

何以笙箫默 2024-07-11 04:26:59

不要手动管理内存,而是在适用的情况下尝试使用智能指针。
看看 Boost 库TR1智能指针
此外,智能指针现在也是 C++ 标准的一部分,称为 C++11

Instead of managing memory manually, try to use smart pointers where applicable.
Take a look at the Boost lib, TR1, and smart pointers.
Also smart pointers are now a part of C++ standard called C++11.

小女人ら 2024-07-11 04:26:59

好问题!

如果您使用 C++ 并且正在开发实时 CPU 和内存 boud 应用程序(例如游戏),您需要编写自己的内存管理器。

我认为你能做的更好的是合并不同作者的一些有趣的作品,我可以给你一些提示:

  • 固定大小分配器被大量讨论,在网络中随处可见

  • 小对象分配由 Alexandrescu 于 2001 年在他的完美著作《现代 C++ 设计》中介绍

  • 一个伟大的进步(已分发源代码)可以在《游戏编程 Gem 7》(2008 年)中一篇名为“高性能堆分配器”的精彩文章中找到,由 Dimitar Lazarov 撰写

  • 可以在 这篇文章

不要开始自己编写一个菜鸟无用的分配器...记录自己第一的。

Great question!

if you are using c++ and you are developing real-time CPU-and-memory boud application (like games) you need to write your own Memory Manager.

I think the better you can do is merge some interesting works of various authors, I can give you some hint:

  • Fixed size allocator is heavily discussed, everywhere in the net

  • Small Object Allocation was introduced by Alexandrescu in 2001 in his perfect book "Modern c++ design"

  • A great advancement (with source code distributed) can be found in an amazing article in Game Programming Gem 7 (2008) named "High Performance Heap allocator" written by Dimitar Lazarov

  • A great list of resources can be found in this article

Do not start writing a noob unuseful allocator by yourself... DOCUMENT YOURSELF first.

差↓一点笑了 2024-07-11 04:26:59

大多数内存泄漏是由于不清楚对象所有权和生命周期造成的。

要做的第一件事就是尽可能在堆栈上进行分配。 这涉及大多数需要为某种目的分配单个对象的情况。

如果您确实需要“新建”一个对象,那么大多数时候它将在其生命周期的剩余时间内拥有一个明显的所有者。 对于这种情况,我倾向于使用一堆集合模板,这些模板是为“拥有”通过指针存储在其中的对象而设计的。 它们是用 STL 矢量和地图容器实现的,但有一些区别:

  • 这些集合不能被复制或分配。 (一旦它们包含对象。)
  • 指向对象的指针将插入其中。
  • 当删除集合时,首先对集合中的所有对象调用析构函数。 (我有另一个版本,它断言是否被破坏且不为空。)
  • 由于它们存储指针,您还可以在这些容器中存储继承的对象。

我对 STL 的看法是,它非常关注值对象,而在大多数应用程序中,对象是独特的实体,不具有在这些容器中使用所需的有意义的复制语义。

Most memory leaks are the result of not being clear about object ownership and lifetime.

The first thing to do is to allocate on the Stack whenever you can. This deals with most of the cases where you need to allocate a single object for some purpose.

If you do need to 'new' an object then most of the time it will have a single obvious owner for the rest of its lifetime. For this situation I tend to use a bunch of collections templates that are designed for 'owning' objects stored in them by pointer. They are implemented with the STL vector and map containers but have some differences:

  • These collections can not be copied or assigned to. (once they contain objects.)
  • Pointers to objects are inserted into them.
  • When the collection is deleted the destructor is first called on all objects in the collection. (I have another version where it asserts if destructed and not empty.)
  • Since they store pointers you can also store inherited objects in these containers.

My beaf with STL is that it is so focused on Value objects while in most applications objects are unique entities that do not have meaningful copy semantics required for use in those containers.

乜一 2024-07-11 04:26:59

valgrind 也是一个在运行时检查程序内存泄漏的好工具。

它可在大多数版本的 Linux(包括 Android)和 Darwin 上使用。

如果您习惯为程序编写单元测试,则应该养成在测试上系统地运行 valgrind 的习惯。 它有可能在早期避免许多内存泄漏。 通常,在简单的测试中比在完整的软件中更容易查明它们。

当然,这个建议对于任何其他内存检查工具仍然有效。

valgrind is a good tool to check your programs memory leakages at runtime, too.

It is available on most flavors of Linux (including Android) and on Darwin.

If you use to write unit tests for your programs, you should get in the habit of systematicaly running valgrind on tests. It will potentially avoid many memory leaks at an early stage. It is also usually easier to pinpoint them in simple tests that in a full software.

Of course this advice stay valid for any other memory check tool.

如日中天 2024-07-11 04:26:59

在整个项目中共享并了解内存所有权规则。 使用 COM 规则可以实现最佳一致性([in] 参数由调用者拥有,被调用者必须复制;[out] 参数由调用者拥有,如果保留引用,被调用者必须复制;等等)

Share and know memory ownership rules across your project. Using the COM rules makes for the best consistency ([in] parameters are owned by the caller, callee must copy; [out] params are owned by the caller, callee must make a copy if keeping a reference; etc.)

盛装女皇 2024-07-11 04:26:59

另外,如果有 std 库类(例如向量),请不要使用手动分配的内存。 如果您违反了该规则,请确保您拥有虚拟析构函数。

Also, don't use manually allocated memory if there's a std library class (e.g. vector). Make sure if you violate that rule that you have a virtual destructor.

一个人的夜不怕黑 2024-07-11 04:26:59

如果您不能/不使用智能指针来执行某些操作(尽管这应该是一个巨大的危险信号),请在代码中键入:

allocate
if allocation succeeded:
{ //scope)
     deallocate()
}

这是显而易见的,但请确保在您之前键入它在范围内键入任意代码

If you can't/don't use a smart pointer for something (although that should be a huge red flag), type in your code with:

allocate
if allocation succeeded:
{ //scope)
     deallocate()
}

That's obvious, but make sure you type it before you type any code in the scope

怼怹恏 2024-07-11 04:26:59

关于如何防止泄漏已经有很多内容,但如果您需要一个工具来帮助您跟踪泄漏,请查看:

There's already a lot about how to not leak, but if you need a tool to help you track leaks take a look at:

划一舟意中人 2024-07-11 04:26:59

尽可能使用智能指针! 整个类别的内存泄漏就会消失。

User smart pointers everywhere you can! Whole classes of memory leaks just go away.

月亮坠入山谷 2024-07-11 04:26:59

C++ 内存管理中流行的一项技术是 RAII。 基本上,您使用构造函数/析构函数来处理资源分配。 当然,由于异常安全性,C++ 中还有一些其他令人讨厌的细节,但基本思想非常简单。

这个问题通常归结为所有权问题。 我强烈推荐阅读 Scott Meyers 的《Effective C++》系列和 Andrei Alexandrescu 的《Modern C++ Design》。

One technique that has become popular with memory management in C++ is RAII. Basically you use constructors/destructors to handle resource allocation. Of course there are some other obnoxious details in C++ due to exception safety, but the basic idea is pretty simple.

The issue generally comes down to one of ownership. I highly recommend reading the Effective C++ series by Scott Meyers and Modern C++ Design by Andrei Alexandrescu.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文