编写C动态库[DSOs]的良好实践(二进制兼容性+内存管理)

voi*_*ptr 23 c linux memory-management shared-libraries binary-compatibility

我有一些写C库的经验,但我从来没有读过任何描述良好实践的正式文档.我的问题主要涉及两个主题:

  1. 如何保持二进制兼容性?(我听说过pImpl成语,d指针)
  2. 如何设计保持向后兼容的接口?

我可以从我的研究中看到的关于二进制兼容性的主要问题是我可以通过使用pImpl惯用法使库二进制兼容,但是即使在使用pImpl时,更改结构/添加新数据成员等也会影响它的二进制兼容性.另外,有没有办法在不实际破坏二进制兼容性的情况下向库中添加新方法/函数?我假设添加这些东西会改变库的大小,从而破坏兼容性.

有没有工具来检查二进制兼容性?

我已经读过这些文章了.还有其他可以仔细阅读的文档吗?

http://en.wikipedia.org/wiki/Opaque_pointer

http://techbase.kde.org/Policies/Binary_Compatibility_Issues_With_C++

此外,是否有文章描述了在设计库接口的上下文中的内存所有权问题.一般惯例是什么?谁拥有记忆,多长时间,谁负责释放记忆等?

R..*_*R.. 21

关键兼容性问题是:

  • 功能签名
  • 库和调用者访问的任何数据的格式
  • 调用者访问的库中的全局变量
  • 由于标头中的宏/内联函数而最终在调用者中的库代码
  • #define/ enum共享标头中的常量值

所以我能给出的最佳指导清单是:

  • 永远不要更改任何公共接口的签名(返回/参数类型).如果您需要扩展的接口,而不是增加新的功能需要更多的参数(认为dupdup2waitwaitpid).
  • 尽可能使用指向完全封装的不透明数据对象的指针,甚至不要在公共头文件中公开这些结构的定义(使它们成为不完整的struct类型).
  • 当你想要共享一个结构时,安排调用者永远不会声明该结构类型的变量,而是在库中调用显式的allocate/free函数.永远不要更改现有成员的类型或删除现有成员; 相反,只在结构的末尾添加新成员.
  • 不要从库中公开全局变量.除非你理解"复制重定位",否则最好不要问为什么.只是不要这样做.
  • 不要将内联函数或包含代码的宏放在库的公共头文件中,除非使用文档化的公开接口保持永久性.如果他们查看不透明数据对象的内部,当您决定更改内部时,它们将导致问题.
  • 不要重新编号现有#define/ enum常量.仅添加具有先前未使用的值的新常量.

如果您遵循这些指导原则,我认为您的覆盖率至少达到95%.

  • 二进制兼容性意味着更改到新版本的库不会破坏针对旧版本编译/链接的应用程序.这很好,所以人们不必保持每个库的"N"不同版本(一旦库开始依赖彼此,它就很容易变成'N ^ M`). (4认同)

lin*_*ild 5

有没有工具来检查二进制兼容性?

ABI Compliance Checker - 用于检查共享C/C++库(DSO)的后向二进制兼容性的工具.

还有其他可以仔细阅读的文档吗?

请参阅这篇关于共享库的二进制兼容性的文章.

如何设计保持向后兼容的接口?

的使用保留/填充字段是保存的相容性的一般方法Ç库.但也有很多其他人.

另外,有没有办法在不实际破坏二进制兼容性的情况下向库中添加新方法/函数?我假设添加这些东西会改变库的大小,从而破坏兼容性.

添加的C函数不会破坏Linux和Mac上DSO的后向二进制兼容性.在Windows和Symbian上也是如此,但是您应该只将新函数添加到.DEF文件的末尾.尽管如此,前向兼容性总是会被添加的功能打破.

添加C++方法会破坏二进制兼容性,当且仅当它们是虚拟纯虚拟的时,因为v-table的布局可能会发生变化.但你的问题似乎只是关于C库.

  • 特别是文章列表中的#19,Ulrich Drepper的dsohowto是编写好的共享库的非常好的介绍. (4认同)