代码之家  ›  专栏  ›  技术社区  ›  Willi Ballenthin

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

  •  14
  • Willi Ballenthin  · 技术社区  · 16 年前

    在其他地方的回复中,我发现了以下片段:

    callee-因此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)
    
    11 回复  |  直到 16 年前
        1
  •  13
  •   Alex Budovski    16 年前

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

    CreateWindow 给你一个 HWND . 你不知道实际发生了什么 WND 结构看起来像,无法触及其字段。

    内核对象句柄也是如此。例如。 CreateEvent 给出 HANDLE . 您只能使用定义良好的API对其进行操作,并使用 CloseHandle()

    关于

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

    struct foo 在收割台中,从而暴露其内部。如果你想改变它,你就有可能破坏(错误地)直接依赖其成员的现有代码。

        2
  •  9
  •   Norman Ramsey    16 年前

    我更喜欢Dave Hanson在年建立的创建/销毁约定 C Interfaces and Implementations :

    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
    

    这种惯例使您不太可能留下悬空的指针。

        3
  •  5
  •   outis    16 年前

    你发布的任何一种方法都是好的形式;前者更接近C++如何处理事物,后者更为客观化。重要的是在代码块中平衡创建和销毁。这种做法属于减少费用的范畴 coupling . 糟糕的做法是使用一个函数来创建一些东西 根据需要执行其他任务 strdup 是的,这使得不查阅文档就很难知道调用方是否必须处理任何东西。

        4
  •  5
  •   nos    16 年前

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

    如果你是用C语言编程,你通常会想要完全控制一切。这意味着让调用者控制在何处以及如何分配结构是一件好事。就我个人而言,如果我不需要不透明结构,我通常会创建两个初始化函数

    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实现有一些好处:

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

    • 调用方无法控制结构foo的分配
    • 您将不得不通过函数调用操作struct foo,这会增加开销
        5
  •  3
  •   Rob H    16 年前

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

        6
  •  3
  •   John Knoeller    16 年前

    好极了 . 让调用者分配内存,调用者可以决定如何分配内存。显然,它们可以在堆栈和堆之间进行选择。但在某些情况下,在多个堆之间也是如此。它们可以将多个分配打包到一个malloc调用中(在需要将数据封送到另一个地址空间时非常有用)。

    在Win32中,有 GlobalAlloc() ,这是分配可在DDE消息中传递给其他应用程序的内存的唯一方法(不再有人关心;)

    VirtualAlloc 这不是经常使用,但有一些属性,使它在一些特殊情况下非常宝贵(您可以在初始化内存后将其从读写更改为只读)。

    CreateFileMapping/MapViewOfFile ,它获取由特定文件支持的内存-写入内存最终写入文件。

        7
  •  3
  •   t0mm13b    16 年前

    我的观点是——有两种方法可以解决这个问题:

    或者,编写一个类似这样的包装函数,以 _alloc 以及相应的包装函数,其结尾为 _free

    简单的优点是:如果程序员无意中引入了内存泄漏,警告就会出现,正如C中的格言“这是错误的” 对于每一个malloc,都应该有一个对应的free,如果您没有它,那么您就有一个漏洞 '. 程序员可以依次提示并说“啊哈……我调用了这个包装器函数 something_alloc 但是没有打电话 something_free ". 你明白要点了吗?无论如何,程序员会为此感谢你的!

    实际上,这取决于代码API定义得有多好。如果您想编写代码来管理内存,从而免除程序员管理内存的责任,那么最好将其包装起来,并赋予它一个特殊的含义,就像我建议的那样,使用下划线后跟“alloc”和“free”。

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

    顺致敬意, 汤姆。

        8
  •  2
  •   Andres    16 年前

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

    当项目变得非常大时,很难弄清楚所有的内存都在哪里。

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

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

        9
  •  1
  •   Menda    16 年前

    我更喜欢这个 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!");
    
        10
  •  1
  •   wisty    16 年前

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

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

    无论你做什么,用惯例告诉人们发生了什么。像这样愚蠢的大人物 pre_allocated_N_array new_result_array (对不起,我不是C专家,不过应该有C约定)对于不用阅读文档就使用你的函数的人来说非常方便。这一切都归结于一致性。

        11
  •  0
  •   Michael Krelin - hacker    16 年前

    如果你自己分配内存,你就可以控制如何分配。可以是堆栈、标准malloc,也可以是应用程序中使用的16个内存管理器之一。

    如果为您分配了内存,不仅您无法控制如何完成,而且您应该知道如何释放内存。大多数库都会免费为您提供一个“免费”函数。

    尽管如此,我仍然不认为有一种“更好”的方法。什么更适合你使用。