Хорошая практика написания динамических библиотек C [DSO] (двоичная совместимость + управление памятью)
У меня есть опыт написания библиотек C, но я никогда не читал никаких официальных документов, описывающих хорошие практики при написании таких библиотек. Мой вопрос касается главным образом 2 тем:
- Как поддерживать двоичную совместимость? (Я слышал о идиоме pImpl, d-указателе)
- Как создать интерфейсы, которые остаются обратно совместимыми?
Самое главное в бинарной совместимости, которую я вижу из моих исследований, это то, что я могу сделать бинарные библиотеки библиотек с помощью идиомы pImpl, но изменение структуры/добавление новых элементов данных и т.д. может повлиять на ее двоичную совместимость даже при использовании pImpl. Кроме того, есть ли способ добавить новые методы/функции в библиотеку, не нарушая при этом бинарную совместимость? Я предполагаю, что добавление этих вещей изменило бы размер, расположение библиотеки, нарушая при этом совместимость.
Есть ли инструмент для проверки совместимости двоичных файлов?
Я уже читал эти статьи. Есть ли другие документы, которые я могу просмотреть?
http://en.wikipedia.org/wiki/Opaque_pointer
http://techbase.kde.org/Policies/Binary_Compatibility_Issues_With_C++
Кроме того, существуют статьи, описывающие проблемы владения памятью в контексте проектирования интерфейсов библиотеки. Каковы общие соглашения? Кому принадлежит память, как долго, кто несет ответственность за освобождение памяти и т.д.?
Ответы
Ответ 1
Ключевыми проблемами совместимости являются:
- сигнатуры функций
- формат любых данных, доступных как библиотекой, так и вызывающим абонентом
- глобальные переменные в библиотеке, доступ к которой осуществляется вызывающим абонентом
- код библиотеки, который попадает в вызывающий объект из-за макросов/встроенных функций в заголовках
-
#define
/enum
постоянные значения в общих заголовках
Итак, лучший список рекомендаций, которые я могу дать, это:
- Никогда не изменяйте подписи (типы возврата/аргументов) любого открытого интерфейса. Если вам нужно расширить интерфейс, вместо этого добавьте новую функцию, которая принимает больше аргументов (подумайте
dup
по сравнению с dup2
или wait
по сравнению с waitpid
).
- В максимально возможной степени используйте указатели на полностью инкапсулированные непрозрачные объекты данных и даже не публикуйте определения таких структур в публичных заголовках (сделайте их неполными
struct
).
- Если вы хотите разделить структуру, устраивайте, чтобы вызывающий объект никогда не объявлял переменные этого типа структуры и вместо этого вызывал явные функции allocate/free в библиотеке. Никогда не изменяйте тип существующих участников или удаляйте существующих участников; вместо этого добавьте новые элементы только в конце структуры.
- Не выставляйте глобальные переменные из библиотек, период. Если вы не понимаете "копирование переездов", лучше не спрашивать, почему. Просто не делай этого.
- Не помещайте встроенные функции или макросы, содержащие код, в общедоступные заголовки библиотеки, если только использование документированного, открытого интерфейса не будет постоянным. Если они ткнут во внутренние объекты непрозрачных объектов данных, они вызовут проблемы, когда вы решите изменить внутренние элементы.
- Не перенумеруйте существующие константы
#define
/enum
. Добавляйте только новые константы с ранее неиспользуемыми значениями.
Если вы будете следовать этим рекомендациям, я думаю, что вы покрыты не менее чем на 95%.
Ответ 2
Есть ли инструмент для проверки совместимости двоичных файлов?
ABI Compliance Checker - инструмент для проверки обратной двоичной совместимости общей библиотеки C/С++ (DSO).
Есть ли другие документы, которые я могу просмотреть?
Смотрите этот длинный список статей по двоичной совместимости разделяемых библиотек.
Как создать интерфейсы, которые остаются обратно совместимыми?
Использование полей зарезервировано/дополнений - это общий метод сохранения совместимости библиотек C. Но есть и много других.
Кроме того, есть ли способ добавить новые методы/функции в библиотеку, не нарушая при этом бинарную совместимость? Я предполагаю, что добавление этих вещей изменило бы размер, расположение библиотеки, нарушая при этом совместимость.
Добавлены функции C не нарушают двоичную совместимость DSO на Linux и Mac. То же самое можно сказать о Windows и Symbian, но вы должны добавлять новые функции только в конец файла .DEF. Однако передняя совместимость всегда прерывается добавленными функциями.
Добавленные методы С++ ломают бинарную совместимость тогда и только тогда, когда они являются виртуальными или чисто-виртуальными, поскольку макет v-table может измениться. Но ваш вопрос, похоже, касается только C.
Ответ 3
Несколько вещей, чтобы добавить к тому, что сказал Р.:
Так как кажется, что вы говорите о C ABI, а не о С++ ABI:
изменение структуры/добавление новых элементов данных и т.д. может повлиять на ее двоичную совместимость даже при использовании pImpl
Это не должно быть при использовании pIMpl - если внешние пользователи объекта имеют только непрозрачный указатель/дескриптор объекта, а только библиотека имеет дело с внутренними элементами структуры, то по определению вещь, которая имеет дело с внутренними структурами, совместимыми с ним.
Есть ли способ добавить новые методы/функции в библиотеку, не нарушая при этом бинарную совместимость? Я предполагаю, что добавление этих вещей изменило бы размер, расположение библиотеки, нарушая при этом совместимость.
Добавление новых функций или изменение размера или макета общей библиотеки не нарушает совместимость с двоичными файлами. Поскольку привязка к адресу функции не выполняется до тех пор, пока в процесс не будет загружена общая библиотека, изменение местоположения целевой функции не является проблемой.
Ответ 4
В терминах документации Как писать общие библиотеки Ульриха Дреппера является обязательным для чтения.