我有一些编写 C 库的经验,但我从未阅读过任何描述编写此类库时的良好做法的正式文档。我的问题主要涉及两个主题:
- 如何保持二进制兼容性? (我听说过 pImpl 惯用法,d 指针)
- 如何设计保持向后兼容的接口(interface)?
从我的研究中我可以看到关于二进制兼容性的主要事情是,我可以通过使用 pImpl 惯用语使库二进制兼容,但即使在使用 pImpl 时更改结构/添加新数据成员等也会影响它的二进制兼容性。另外,有没有办法在不破坏二进制兼容性的情况下向库添加新方法/函数?我假设添加这些东西会改变库的大小和布局,从而破坏兼容性。
是否有检查二进制兼容性的工具?
我已经阅读了这些文章。还有其他我可以细读的文档吗?
http://en.wikipedia.org/wiki/Opaque_pointer
http://techbase.kde.org/Policies/Binary_Compatibility_Issues_With_C++
此外,是否有文章描述了在设计库接口(interface)的上下文中内存的所有权问题。一般约定是什么?谁拥有内存,多长时间,谁负责释放内存等?
最佳答案
关键的兼容性问题是:
- 函数签名
- 库和调用者访问的任何数据的格式
- 调用者访问的库中的全局变量
- 由于 header 中的宏/内联函数而在调用者中结束的库代码
#define
/enum
共享 header 中的常量值
所以我能给出的最佳指南列表是:
- 切勿更改任何公共(public)接口(interface)的签名(返回/参数类型)。如果您需要扩展一个接口(interface),而不是添加一个接受更多参数的新函数(想想
dup
与dup2
或wait
与waitpid
). - 尽可能使用指向完全封装的不透明数据对象的指针,甚至不要在公共(public) header 中公开此类结构的定义(使它们成为不完整的
struct
类型)。 - 当你确实想要共享一个结构时,安排调用者从不声明该结构类型的变量,而是调用库中的显式分配/释放函数。切勿更改现有成员的类型或删除现有成员;相反,只在结构的末尾添加新成员。
- 不要暴露库中的全局变量,句号。除非您了解“复制重定位”,否则最好不要问为什么。只是不要这样做。
- 不要将内联函数或包含代码的宏放入您的库的公共(public) header 中,除非使用将永久保留的已记录的公开接口(interface)。如果他们戳穿不透明数据对象的内部结构,当您决定更改内部结构时,他们会导致问题。
- 不要重新编号现有的
#define
/enum
常量。仅添加具有以前未使用的值的新常量。
如果您遵循这些准则,我认为您至少已覆盖 95%。
关于c - 编写 C 动态库 [DSOs] 的良好实践(二进制兼容性 + 内存管理),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7217258/