如果我在某个作用域中退出(),但并不是所有的数据结构都在该作用域中,那么我该如何释放我的结构?
我感觉我需要将所有内容包装在一个伪异常处理程序中,并让处理程序处理释放,但这仍然看起来很丑陋,因为它必须知道我可能需要或不需要释放的所有内容...
当调用exit()函数时,您无需担心释放内存。当进程退出时,操作系统将释放所有相关内存。
该过程不需要您手动释放内存。malloc
的包装器,并以有纪律的方式使用它们。跟踪您分配的内存(可能是在链表中),并使用包装器退出以枚举您的内存以释放它。您还可以使用链表结构的附加参数和成员来命名内存。在分配的内存高度依赖于范围的应用程序中,您将发现自己泄漏内存,这可以是卸载内存并对其进行分析的好方法。exit()
而不必担心程序已分配的内存。exit()
可能无法清除所有东西。通常我看到的情况是当单个函数由于错误而返回时,它们会确保清理任何它们自己分配的东西。在调用 10 个函数后,你不会看到任何 exit()
调用。每个函数都会在返回时指示错误,并清理自己分配的内存。原始的 main()
函数(如果你愿意的话 - 它可能不叫 main()
)会检测错误、清理任何它已分配的内存并采取适当的措施。refcnt
(引用计数)值。(垃圾收集器基本上也是这样做的。)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使用方式,如果一贯地应用,可以使代码更清晰、更简单、更易于维护。如果没有它,你无法通过代码实现一个简单、文档化的拆除路径。
如果你想在真正的商业代码中看到这个例子,请看看MPS中的arena.c(巧合的是,它是一个内存管理系统)。
这是一种类似于析构函数的贫民版try...finish处理程序。
我现在听起来像一个老古董了,但在我多年的C代码开发经验中,缺乏明确的错误路径通常是一个非常严重的问题,特别是在网络代码和其他不可靠的情况下。引入它们有时会给我带来相当多的咨询收入。
关于你的问题还有很多其他的事情要说——我只是留下这个模式,以防有用。
NULL
初始化了你的baz
和bar
,并且使你的销毁函数能够优雅地处理NULL
参数(就像free
一样),那么你就可以只使用一个fail:
标签,使它变得更加简洁。 - Patrick Schlüter迈克尔的建议很好 - 如果您正在退出,您不需要担心释放内存,因为系统会自动回收它。
其中一个例外是共享内存段 - 至少在 System V 共享内存下。这些段可以比创建它们的程序更长时间地存在。
到目前为止未提及的一个选项是使用基于区域的内存分配方案,构建在标准的 malloc()
之上。如果整个应用程序使用单个区域,则清理代码可以释放该区域,并且所有内容都会一次性释放。(APR - Apache Portable Runtime - 提供了类似的池功能;David Hanson 的 "C Interfaces and Implementations" 提供了基于区域的内存分配系统;如果您愿意,我也写了一个您可以使用的。) 您可以将其视为“穷人的垃圾回收”。