Страницы

Рассогласованность runtime`ов библиотеки и программы

Давеча столкнулся с неприятной особенностью при разработке библиотеки на языке Си.
Есть в ней функции, возвращающие указатели на динамически выделенные массивы стандартного типа с помощью malloc(), по большей части это обычные Си-строки. Я рассчитывал, что освобождать эти данные из кода основной программы нужно стандартной функцией free(). Это разумно для компонентной системы, библиотека и программа, ее использующая, динамически подключаются к одной и той же стандартной библиотеке, в результате чего используют согласованные средства для выделения и освобождения памяти.

Основная разработка ведется в Linux, и в ней нарушения этого принципа не наблюдалось. Для любых доступных комбинаций компиляторов (gcc, clang, tcc, tendracc) для библиотеки и приложения никаких накладок не было замечено. Собранная библиотека оказалась переносима между разными версиями дистрибутива.

А вот в Windows меня ждало небольшое разочарование. Библиотеку для нее собирали средствами mingw, а приложение - с помощью Visual Studio. Несмотря на заверения о том, что mingw использует родной для Windows c-runtime,  какую-то несовместимость он вносит. При попытке средствами программы освободить память, выделенную в библиотеке, происходит крах.

Есть категоричное мнение, что при правильной архитектуре освобождать нужно теми же средствами, что использовались для выделения. То есть в пару к функции, возвращающей указатель на динамически созданный объект, должна быть создана функция его освобождения.  Тогда проблемы рассогласованности не возникнет.

Разумно, тем не менее, я считаю такой подход не всегда адекватным.
Он выглядит логично для сложных структур, но абсурдно для массива литер. Согласно нему создателям функций вроде strdup и asprintf, нужно было создать и strdupfree, asprintfree. Это может приводить к лишним накладным расходам как по времени выполнению, так и по написанию кода, а также дополнительным ошибкам. К примеру, если была получена выделенная строка, которую нужно передать в структуру - ее владельца, то туда же надо передать и информацию о способе освобождения строки, или сделать копию, а оригинал соответственно освободить. Сколько интересных возможностей вместо простой передачи указателя.

Проблема не исчерпывается только выделением памяти, есть ведь и другие ресурсы. Можно ли читать файл в библиотеке, открытый средствами программы? Или нужно предоставить библиотечную обертку над системной функцией для открытия файла? Что делать, если файл недоступен по имени?

Получается, что библиотека плохо встраивается в остальной код, и некоторые задачи могут выродиться в сплошное согласование вызовов функций, что может поставить вопрос об их целесообразности.

Поэтому мне больше по душе расчет на согласованность runtime`ов. Но что делать, если это нельзя гарантировать? Я нашел для себя приемлемое решение.

Нужна функция инициализации, заключающейся в том, чтобы передать из основной программы указатели на спорные функции, используемые в библиотеке. Именно их и будет вызывать библиотека вместо системных, что позволит ей тесно встроиться в работу основной программы. Работа библиотеки без инициализации должна сопровождаться крахом с выводом доходчивого сообщения о причине падения.

Дополнительным плюсом является повышенная гибкость для пользователя библиотеки, предоставляющая ему больше контроля.

Неприятность заключается в том, что функции вроде strdup(), уже не заставить использовать чужой malloc(), о чем можно забыть при написании библиотеки. Так что все еще остается простор для ошибок.

Комментариев нет:

Отправить комментарий