C API设计:谁应该分配?

C API设计:谁应该分配?,c,api,memory-management,malloc,C,Api,Memory Management,Malloc,在C API中分配内存的正确/首选方法是什么 首先,我可以看到两种选择: 1) 让调用者执行所有(外部)内存处理: myStruct *s = malloc(sizeof(s)); myStruct_init(s); myStruct_foo(s); myStruct_destroy(s); free(s); \u init和\u destroy功能是必需的,因为内部可能会分配更多内存,必须在某个地方处理 这样做的缺点是较长,但在某些情况下也可以消除malloc(例如,可以向其传递堆栈分配

在C API中分配内存的正确/首选方法是什么

首先,我可以看到两种选择:

1) 让调用者执行所有(外部)内存处理:

myStruct *s = malloc(sizeof(s));
myStruct_init(s);

myStruct_foo(s);

myStruct_destroy(s);
free(s);
\u init
\u destroy
功能是必需的,因为内部可能会分配更多内存,必须在某个地方处理

这样做的缺点是较长,但在某些情况下也可以消除malloc(例如,可以向其传递堆栈分配的结构:

int bar() {
    myStruct s;
    myStruct_init(&s);

    myStruct_foo(&s);

    myStruct_destroy(&s);
}
此外,调用方还需要知道结构的大小

2) 在
\u init
中隐藏
malloc
s,在
\u destroy
中隐藏
free
s

优点:代码更短,因为无论如何都要调用函数。完全不透明的结构

缺点:无法传递以其他方式分配的结构

myStruct *s = myStruct_init();

myStruct_foo(s);

myStruct_destroy(foo);

我现在倾向于第一个案例;再说一遍,我不知道C API设计。

我最喜欢的井式设计C API示例是使用您描述的方法2

尽管方法#1的另一个优点不仅仅是可以在堆栈上分配对象,还可以多次重用同一实例。如果这不是一个常见的用例,那么#2的简单性可能是一个优势


当然,这只是我的观点:)

两者都是可以接受的——正如您所指出的,它们之间存在权衡

现实世界中有大量这两种方法的例子——正如所说,GTK+使用第二种方法;OpenSSL就是一个使用第一种方法的例子。

第二种方法的另一个缺点是调用方无法控制如何分配内容。这可以通过为客户机提供一个API来注册自己的分配/解除分配函数(就像SDL一样)来解决,但即使这样也可能不够细粒度

#1的缺点是,当输出缓冲区大小不固定(例如字符串)时,它无法正常工作。充其量,您将需要提供另一个函数来首先获取缓冲区的长度,以便调用者可以分配它。在最坏的情况下,根本不可能有效地做到这一点(即,与一次性计算和复制相比,在单独路径上计算长度过于昂贵)

#2的优点是,它允许您严格地将数据类型公开为不透明指针(即声明结构,但不定义结构,并一致地使用指针)。然后,您可以在库的未来版本中更改结构的定义,同时客户端在二进制级别上保持兼容。对于#1,您必须要求客户端以某种方式指定结构内部的版本(例如Win32 API中的所有
cbSize
字段),然后手动编写可以处理较旧和较新版本的结构的代码,以便随着库的发展保持二进制兼容


一般来说,如果您的结构是透明的数据,并且不会随着库的未来小修订而改变,那么我会选择#1。如果它是一个或多或少复杂的数据对象,并且您希望完全封装以防将来开发时出错,请使用#2。

两者在功能上是等效的。但是,在我看来,方法2更容易使用。选择2而不是1的几个原因是:

  • 它更直观。在使用
    myStruct\u Destroy
    销毁对象(显然)之后,为什么我必须对该对象调用
    free

  • 对用户隐藏myStruct的详细信息。他不必担心它的尺寸等等

  • 在方法#2中,
    myStruct_init
    不必担心对象的初始状态

  • 您不必担心由于用户忘记调用
    free
    而导致内存泄漏


  • 但是,如果您的API实现是作为一个单独的共享库提供的,那么方法2是必须的。要将模块与
    malloc
    /
    new
    free
    /
    delete
    在不同编译器版本中的实现中的任何不匹配隔离开来,您应该将内存分配和反分配留给自己。请注意,这比C++更真实,比C.

    < P>我第一个方法所遇到的问题与其说是调用方的时间长,还不如说现在API被扩展了,因为它不知道它所接收的内存是如何分配的。调用方并不总是提前知道它需要多少内存(想象一下,如果您试图实现一个向量)

    您没有提到的另一个选项是传入api用作分配器的函数指针,这在大多数情况下都是多余的。这不允许您使用堆栈,但允许您使用内存池替换malloc的使用,这仍然使api能够控制何时分配内存

    至于哪种方法是合适的api设计,它在C标准库中是双向的。strdup()和stdio使用第二种方法,而sprintf和strcat使用第一种方法。就个人而言,我更喜欢第二种方法(或第三种),除非1)我知道我永远不需要realloc,2)我希望我的对象的生命周期很短,因此使用堆栈非常方便

    编辑:
    实际上还有另外一个选择,这是一个有着显著先例的坏选择。您可以像strtok()使用statics那样进行操作。不太好,只是为了完整性而提到。

    两种方法都可以,我倾向于使用第一种方法,因为我使用的很多C语言都是针对嵌入式系统的,所有的内存要么是堆栈上的微小变量,要么是静态分配的。这样就不会出现内存不足的情况,要么一开始就有足够的内存,要么一开始就被搞砸了。很高兴知道什么时候有2K的Ram:-),所以我所有的库都像#1,其中假定分配了内存

    但是这个我
    myStruct *myStruct_create ()
    {
        myStruct *s = malloc(sizeof(*s));
        if (s) 
        {
            myStruct_init(s);
        }
        return (s);
    }
    
    void myStruct_destroy (myStruct *s)
    {
        myStruct_terminate(s);
        free(s);
    }
    
    myStruct *s = myStruct_init(malloc(sizeof(myStruct)));
    
    #define NEW(T) (T ## _init(malloc(sizeof(T))))
    
    myStruct *s = NEW(myStruct);
    
    myStruct *s = myStruct_init();
    
    myStruct_foo(s);
    
    myStruct_destroy(s);
    
    myStruct *s;
    int ret = myStruct_init(&s);  // int myStruct_init(myStruct **s);
    
    myStruct_foo(s);
    
    myStruct_destroy(s);