问题描述
在C API中分配内存的正确/首选方法是什么?
我一开始可以看到两个选项:
1)让呼叫者完成所有(外部)内存处理:
myStruct *s = malloc(sizeof(s)); myStruct_init(s); myStruct_foo(s); myStruct_destroy(s); free(s);
_init和_destroy函数是必要的
这具有更长的缺点,但是在某些情况下也可以消除malloc(例如,可以通过堆栈分配的结构:
int bar() { myStruct s; myStruct_init(&s); myStruct_foo(&s); myStruct_destroy(&s); }
另外,呼叫者有必要知道结构的大小.
2)_init和free s in _destroy>.
优点:较短的代码,因为无论如何这些函数都会被调用.完全不透明的结构.
缺点:不能以不同的方式通过分配的结构.
myStruct *s = myStruct_init(); myStruct_foo(s); myStruct_destroy(foo);
我目前正在倾向于第一种情况;再说一次,我不知道C API设计.
推荐答案
我最喜欢设计的C API的示例是 gtk+使用该方法2的方法2你描述.
尽管您的方法1的另一个优点不仅是您可以在堆栈上分配对象,还可以多次 recuse recuse 相同的实例.如果这不是常见用例,那么#2的简单性可能是一个优势.
当然,这只是我的看法:)
其他推荐答案
#2的另一个缺点是呼叫者无法控制事物的分配方式.可以通过为客户提供自己的API注册自己的分配/Deallocation功能(就像SDL一样)来解决这一问题,但即使不是足够细粒度.
#1的缺点是,当输出缓冲区不是固定尺寸(例如字符串)时,它的运行不佳.充其量,您将需要提供另一个功能才能首先获得缓冲区的长度,以便呼叫者可以分配它.在最坏的情况下,根本不可能有效地这样做(即单独的路径上的计算长度过于昂贵,而不是一次计算和复制).
#2的优点是,它允许您严格地将数据类型视为不透明的指针(即声明结构但不定义结构,并始终使用指针).然后,您可以在库的将来版本中更改结构的定义,而客户在二进制级别上保持兼容.使用#1,您必须通过要求客户以某种方式指定结构内部的版本(例如,Win32 API中的所有cbSize字段),然后手动编写可以处理较旧版本和较新版本的代码随着库的发展,保持二进制的结构.
通常,如果您的结构是透明的数据,这些数据不会随着库的未来次要修订而变化,我将选择#1.如果它或多或少是复杂的数据对象,并且您希望完整的封装使其置于未来的开发中,请使用#2.
其他推荐答案
每次数字2.
为什么?因为使用方法1,您必须将实现详细信息泄漏到呼叫者.呼叫者必须至少知道 结构有多大.如果不重新编译任何使用它的代码,您就无法更改对象的内部实现.
问题描述
What is the proper/preferred way to allocate memory in a C API?
I can see, at first, two options:
1) Let the caller do all the (outer) memory handling:
myStruct *s = malloc(sizeof(s)); myStruct_init(s); myStruct_foo(s); myStruct_destroy(s); free(s);
The _init and _destroy functions are necessary since some more memory may be allocated inside, and it must be handled somewhere.
This has the disadvantage of being longer, but also the malloc can be eliminated in some cases (e.g., it can be passed a stack-allocated struct:
int bar() { myStruct s; myStruct_init(&s); myStruct_foo(&s); myStruct_destroy(&s); }
Also, it's necessary for the caller to know the size of the struct.
2) Hide mallocs in _init and frees in _destroy.
Advantages: shorter code, since the functions are going to be called anyway. Completely opaque structures.
Disadvantages: Can't be passed a struct allocated in a different way.
myStruct *s = myStruct_init(); myStruct_foo(s); myStruct_destroy(foo);
I'm currently leaning for the first case; then again, I don't know about C API design.
推荐答案
My favourite example of a well-design C API is GTK+ which uses method #2 that you describe.
Although another advantage of your method #1 is not just that you could allocate the object on the stack, but also that you could reuse the same instance multiple times. If that's not going to be a common use case, then the simplicity of #2 is probably an advantage.
Of course, that's just my opinion :)
其他推荐答案
Another disadvantage of #2 is that the caller doesn't have control over how things are allocated. This can be worked around by providing an API for the client to register his own allocation/deallocation functions (like SDL does), but even that may not be sufficiently fine-grained.
The disadvantage of #1 is that it doesn't work well when output buffers are not fixed-size (e.g. strings). At best, you will then need to provide another function to obtain the length of the buffer first so that the caller can allocate it. At worst, it is simply impossible to do so efficiently (i.e. computing length on a separate path is overly expensive over computing-and-copying in one go).
The advantage of #2 is that it allows you to expose your datatype strictly as an opaque pointer (i.e. declare the struct but don't define it, and use pointers consistently). Then you can change the definition of the struct as you see fit in future versions of your library, while clients remain compatible on binary level. With #1, you have to do it by requiring the client to specify the version inside the struct in some way (e.g. all those cbSize fields in Win32 API), and then manually write code that can handle both older and newer versions of the struct to remain binary-compatible as your library evolves.
In general, if your structs are transparent data which will not change with future minor revision of the library, I'd go with #1. If it is a more or less complicated data object and you want full encapsulation to fool-proof it for future development, go with #2.
其他推荐答案
Method number 2 every time.
Why? because with method number 1 you have to leak implementation details to the caller. The caller has to know at least how big the struct is. You can't change the internal implementation of the object without recompiling any code that uses it.