我目前正在开发一个基于C的应用程序,因为它以非反模式方式释放内存。我是一名记忆管理爱好者。
我的主要问题是我在各种不同的范围内声明了内存结构,并且通过引用其他函数来传递这些结构。其中一些函数可能会抛出错误并退出()。
如果我在一个范围内退出(),但是并非所有数据结构都在该范围内,我如何解放我的结构?
我感觉我需要将它全部包装在伪造的异常处理程序中并让处理程序处理释放,但这看起来仍然很难看,因为它必须知道我可能需要或不需要释放的所有内容。
答案 0 :(得分:3)
调用exit()时,您无需担心释放内存。当进程退出时,操作系统将释放所有相关的内存。
答案 1 :(得分:3)
考虑malloc
的包装并以纪律的方式使用它们。跟踪您分配的内存(可能在链接列表中)并使用包装器退出以枚举您的内存以释放它。您可以还使用附加参数和链接列表结构的成员为内存命名。在分配内存高度依赖于内存的应用程序中,您会发现自己泄漏内存,这可能是转储内存并进行分析的好方法。
<强>更新强> 在您的应用程序中进行线程化将使这非常复杂。请参阅有关线程问题的其他答案。
答案 2 :(得分:3)
我认为要适当地回答这个问题,我们需要了解整个程序(或系统,或任何情况)的架构。
答案是:这取决于。您可以使用许多策略。
正如其他人所指出的,在现代桌面或服务器操作系统上,您可以exit()
而不用担心程序分配的内存。
例如,如果您在exit()
可能无法清除所有内容的嵌入式操作系统上进行开发,则此策略会发生变化。通常我看到的是当个别函数由于错误而返回时,他们确保清理他们自己分配的任何东西。在调用10个函数后,您将看不到任何exit()
个调用。每个函数在返回时反过来指示错误,并且每个函数将在其自身之后清理。原始main()
函数(如果您将 - 它可能不会被称为main()
)将检测错误,清除它已分配的任何内存,并采取适当的操作。
当你只有范围内的范围时,它不是火箭科学。如果您有多个执行线程和共享数据结构,那么困难之处。然后,您可能需要一个垃圾收集器或一种计算引用的方法,并在结构的最后一个用户完成后释放内存。例如,如果查看BSD网络堆栈的源代码,您会看到它在某些需要长时间保持“活动”的结构中使用refcnt
(引用计数)值。并在不同用户之间共享。 (这基本上就是垃圾收集者所做的事情。)
答案 3 :(得分:1)
您可以为范围/函数之间共享的malloc内存创建一个简单的内存管理器。
当你使用malloc时注册它,当你释放它时取消注册它。在调用exit之前,有一个释放所有已注册内存的函数。
它增加了一些开销,但它有助于跟踪内存。它还可以帮助您找到讨厌的内存泄漏。
答案 4 :(得分:1)
迈克尔的建议是合理的 - 如果你要离开,你不需要担心释放内存,因为无论如何系统都会收回它。
一个例外是共享内存段 - 至少在System V共享内存下。这些段可以比创建它们的程序持续更长时间。
目前尚未提及的一个选项是使用基于竞技场的内存分配方案,该方案建立在标准malloc()
之上。如果整个应用程序使用单个竞技场,则清理代码可以释放该竞技场,并且所有应用程序都会立即释放。 (APR - Apache Portable Runtime - 提供了一个我相信类似的池功能; David Hanson的“C接口和实现”提供了一个基于竞技场的内存分配系统;如果你愿意,我已经编写了一个你可以使用的功能。)你可以把这想象成“穷人的垃圾收集”。
作为一般的内存规则,每次动态分配内存时,都应该了解哪些代码会释放它以及什么时候可以释放它。有一些标准模式。最简单的是“在此函数中分配;在此函数返回之前释放”。这使得内存在很大程度上受到控制(如果你没有在包含内存分配的循环上运行太多迭代),并对其进行范围调整,以便它可以用于当前函数及其调用的函数。显然,您必须合理地确定您调用的函数不会松散(缓存)指向数据的指针,并在您释放并重用内存后尝试重用它们。
下一个标准模式以fopen()
和fclose()
为例;有一个函数可以分配一个指向某个内存的指针,该指针可以被调用代码使用,然后在程序完成后释放。但是,这通常与第一种情况非常相似 - 在调用fclose()
的函数中调用fopen()
通常是个好主意。
大多数剩余的“模式”在某种程度上是 ad hoc 。
答案 5 :(得分:1)
人们已经指出,如果您只是在出现错误时退出(或中止)代码,则可能不需要担心释放内存。但是为了以防万一,这是我开发的模式,并且在出错时用于创建和拆除资源。注意:我在这里展示一个模式,以表达观点,而不是编写真正的代码!
int foo_create(foo_t *foo_out) {
int res;
foo_t foo;
bar_t bar;
baz_t baz;
res = bar_create(&bar);
if (res != 0)
goto fail_bar;
res = baz_create(&baz);
if (res != 0)
goto fail_baz;
foo = malloc(sizeof(foo_s));
if (foo == NULL)
goto fail_alloc;
foo->bar = bar;
foo->baz = baz;
etc. etc. you get the idea
*foo_out = foo;
return 0; /* meaning OK */
/* tear down stuff */
fail_alloc:
baz_destroy(baz);
fail_baz:
bar_destroy(bar);
fail_bar:
return res; /* propagate error code */
}
我可以打赌我会得到一些评论说“这很糟糕,因为你使用goto”。但这是goto的规范和结构化使用,如果一致地应用,使代码更清晰,更简单,更容易维护。如果没有它,你无法通过代码实现一个简单的,记录在案的拆卸路径。
如果您想在实际使用的商业代码中看到这一点,请查看arena.c from the MPS(巧合的是内存管理系统)。
这是一个穷人的尝试...完成处理程序,并给你一些像破坏者的东西。
我现在听起来像是一个灰熊,但在我多年研究其他人的C代码时,缺乏明确的错误路径通常是一个非常严重的问题,尤其是在网络代码和其他不可靠的情况下。介绍它们偶尔会给我带来相当多的咨询收入。
关于你的问题,还有很多其他的事情要说 - 我只是假设有用这种模式。
答案 6 :(得分:0)
非常简单,为什么没有引用计数实现,所以当你创建一个对象并传递它时,你增加和减少引用计数的数字(如果你有多个线程,记得是原子的)。
这样,当一个对象不再使用(零引用)时,你可以安全地删除它,或者在引用计数减量调用中自动删除它。
答案 7 :(得分:0)