应该什么时候C函数返回新分配的内存?

在其他地方的回复中,我找到了以下代码段:

一般来说,让C调用者分配内存而不是被调用者更好 – 因此在我看来strcpy是一个“更好”的函数,而不是strdup。

我可以看到这是一个有效的模式,但为什么它会被认为更好? 遵循这种模式是否有优势? 或不?

最近我写了大量的代码看起来像:

struct foo *a = foo_create(); // do something with a foo_destroy(a); 

如果foo不仅仅是一个扁平结构,那么我想我可以将所有初始化完成一步。 另外,假设结构应该在堆上。 为什么这样做会更好:

 struct foo *a = malloc(sizeof(foo)); foo_init(a); // do something with a foo_destroy(a) 

每当你想要一个不透明的结构,并且不想在头文件中暴露它的内部。 你的foo_create()示例说明了这一点。

另一个例子是Windows API。 例如, CreateWindow为您提供HWND 。 您不知道实际的WND结构是什么样的,并且无法触及其字段。

与内核对象句柄相同。 例如, CreateEvent给出一个HANDLE 。 您只能使用定义良好的API对其进行操作,并使用CloseHandle()其关闭。

回覆:

 struct foo *a = malloc(sizeof(foo)); 

这要求您在标头中定义struct foo ,从而暴露其内部。 如果要在轨道上进行更改,则可能会破坏(错误地)直接依赖其成员的现有代码。

让调用者分配内存的主要优点是它简化了接口,并且调用者拥有内存是完全明确的。 正如您的create / destroy示例所示,简化并不是很好。

我更喜欢Dave Hanson在C接口和实现中建立的创建/销毁约定:

 struct foo *foo_new(...); // returns result of malloc() void foo_free(struct foo **foop); // free *foop's resources and set *foop = NULL 

你遵循这样的惯例:

 struct foo *a = foo_new(); ... foo_free(&a); // now `a` is guaranteed to be NULL 

这个约定使你不太可能留下一个悬垂的指针。

你发布的任何一种方法都是好的forms; 前者更接近于C ++处理事物的方式,后者更像是Objective-C。 重要的是在代码块中平衡创建和销毁。 这种做法属于减少耦合的范畴。 有一个不好的做法是拥有一个创建某些function执行其他任务的function,就像strdup那样,这使得更难以知道调用者是否必须在不查阅文档的情况下处理任何事情。

两种方法都很好。 考虑所有FILE *操作函数,它们不允许您自己分配文件。

如果您使用C编程,您通常需要完全控制所有内容。 这意味着让调用者可以控制在何处以及如何分配结构是一件好事。 就个人而言,如果我不需要不透明的结构,我通常会为初始化创建2个函数

 int foo_init(struct foo *f); // allows the caller to allocate 'f' //however is suitable struct foo * new_foo(void); // mallocs and calls foo_init, for convenience. 

如果需要,相应的

  void foo_free(struct foo *f ); //frees and destroys 'f' void foo_destroy(struct foo *f); //cleans up whatever internal stuff 'f' has, // but does not free 'f' itself 

如果您希望调用者将结构视为不透明,您只需提供struct foo * new_foo(void); 不暴露struct foo实现有一些好处:

  • 呼叫者不允许通过直接访问成员来寻找或执行潜在的危险捷径。
  • 您可以在不破坏现有二进制文件的情况下更改struct foo(您没有破坏ABI),如果您正在实现库,则可能是一个大问题。
  • 您的公共头文件不需要公开struct foo的实现和其他必需的头文件

缺点

  • 调用者无法控制struct foo的分配
  • 你将不得不通过函数调用来操纵struct foo

通过在同一个函数(或源文件)中分配和释放内存,您可以更轻松地识别潜在的内存泄漏(或说服自己没有),而不必跳到程序中的不同位置。 如果被调用者分配内存,则应该进行重新分配的位置不明确。 相反,通过让调用者这样做,该代码对内存负有“全部责任”。

最重要的是,一致性是关键。 选择一种方法并坚持下去。

在朋友中更好 。 让调用者分配内存,调用者可以决定如何分配内存。 他们可以明显地选择堆栈和堆栈。 但在某些情况下也会在多个堆之间。 他们可以将多个分配打包到单个malloc调用中(当您需要将数据封送到另一个地址空间时很有用)。

在Win32中,有GlobalAlloc() ,它是分配可以在DDE消息中传递给其他应用程序的内存的唯一方法。 (不是任何人都在乎;)

在Win32中我们也有VirtualAlloc ,它不经常使用,但有一些属性使它对某些特殊情况非常宝贵。 (初始化后,您可以将内存从读写更改为只读)。

还有CreateFileMapping/MapViewOfFile ,它可以获取由特定文件支持的内存 – 写入内存,最后写入文件。

我确信Unix具有相同的专用内存分配function。

我对它的看法是这样的 – 有两种方法可以解决这个问题:

如果你编写一个分配内存的函数,在函数上面写一条注释,表明内存管理的责任在于程序员,即显式释放内存,从而将内存管理的负担传递给负责的程序员。

或者,编写一个类似于此的包装函数,以_alloc结尾,并以_alloc结尾的相应包装函数,这样,您定义了一组记录良好的例程,使程序员更容易阅读。

这个简单的优点是:如果程序员非故意引入了内存泄漏,就会出现警告,因为C中的格言是“ 对于每个malloc,应该有相应的免费,如果你没有它,那么你有泄漏 ‘。 程序员反过来可以提示并说“Aha..I我称之为包装函数something_alloc但没有调用something_free ”。 你得到了要点? 无论如何,程序员会感谢你的!

实际上,这取决于代码API的定义。 如果你想编写代码来管理内存,从而让程序员免于管理内存的责任,最好将其包装并赋予它特殊的含义,正如我所建议的那样使用下划线后跟’alloc’和’free ”。

这将赢得你的荣誉和尊重,因为将阅读和使用你的代码的程序员会说 – ‘谢谢芽’,最终结果是每个人都会很高兴。

希望这会有所帮助,最好的问候,汤姆。

这一切都归结为建立内存的所有权。

当项目变得非常大时,很难弄清楚所有内存的去向。

在C ++中,我们经常使用诸如foo_create()示例之类的工厂来解决这个问题。 该工厂知道如何设置foo对象,并且可以轻松跟踪分配的内存量和释放量。

虽然可以在C中完成类似的操作,但通常我们只需确保程序的每一层都清理它使用的内存。 因此,审阅者可以浏览代码以确保每个malloc具有匹配的免费。 当分配过于嵌套时,很快就会发现内存泄漏发生的位置。

顺便说一句,为了从初始化程序返回错误值,我倾向于倾向于使用与分配分开的初始化程序。 如果你只是调用foo_create(),并返回一个空指针,则不清楚创建是否由于内存不足或其他原因而失败。 养成在init函数上具有返回值的习惯可以节省大量的调试时间。

我更喜欢GLib风格(你提到的第一款)。 对我来说,选择这种风格使其更像面向对象。 您的方法负责创建和销毁结构,因此您不必与结构的内部进行对抗。 这种方法可以使您的代码也减少错误。

一个GString示例:

 GString *s; s = g_string_new(); // Not in this case, but sometimes you can // find this: if (s == NULL) printf("Error creating the object!"); 

让调用者分配内存更好,因为您可以通过手动回收旧数据结构来节省内存分配。 当你有很多N大小的数组时,这在mathy应用程序中很有用。 请记住,内存分配非常慢。

另一方面,如果数组的大小只能由函数确定(即结果的大小未知),那么被调用者应该分配。

无论你做什么,都要用惯例来告诉人们发生了什么。 像pre_allocated_N_arraynew_result_array这样的大愚蠢的名字(对不起,我不是C专家,虽然应该有C约定)对于那些在不阅读文档的情况下使用你的function的人来说非常方便。 这一切都归结为一致性。

如果你自己分配你的记忆,你可以控制你如何做到这一点。 无论是堆栈,标准malloc还是您在应用程序中使用的十六个内存管理器之一。

如果为你分配了内存,你不仅无法控制它是如何完成的,而是你应该知道如何释放内存。 好吧,大多数图书馆都会免费为您提供“免费”function。

话虽如此,我仍然认为没有一种“更好”的方法。 什么更适合您的使用。