У меня следующая ситуация. У меня есть корневой проект, который содержит подпроект. Этот подпроект задуман как библиотека, которую вы можете создать как отдельно, так и вместе с другим проектом.
В библиотеке мне нужно выделить память в нескольких местах. Однако я хотел бы иметь возможность переопределить эту функцию.
Например, в библиотеке у меня есть вызов malloc(ptr). И я хочу иметь возможность заменить эти вызовы потребителем библиотеки (в данном случае корневым проектом). В этом случае корневой проект должен переопределить/заменить вызовы в библиотеке без зависимости библиотеки от корневого проекта.
Для дополнительного контекста я использую этот способ MACRO, потому что он более эффективен, чем, например, использование указателей на функции. В этом случае меня интересует высокопроизводительное распределение.
Мой вопрос сводится к следующему: Как лучше всего расширять подобные библиотеки?
Я нашел несколько связанных вопросов, но меня особенно интересуют лучшие практики для этих вещей; что это возможно, я уже знаю.
На данный момент мне удалось достичь того, чего я хочу, это следующее.
Теперь при сборке библиотеки это работает правильно, корневой проект правильно «переопределяет» HDR_MALLOC библиотеки. Когда rootproject использует библиотеку, она вызывает реализацию корневого проекта вместо своей собственной.
Используйте указатели функций, например.
// lib.h
void libfoo(int one, double two, struct bar *three, void *(*alocfx)(size_t));
и в основном проекте вызовите библиотечную функцию
#include <stdlib.h>
#include "lib.h"
int main(void) {
libfoo(1, 3.14159, NULL, malloc);
}
Вы можете реализовать библиотечную функцию так, что если указатель функции равен NULL
, он будет использовать malloc()
стандартной библиотеки. Конечно, добавьте это поведение в документацию библиотеки :)
Спасибо за разъяснения. Я имел в виду, что внутри библиотеки (не доступной для основного проекта) вызывается функция (в данном случае malloc). Потребитель библиотеки просто вызывает открытые функции, которые внутренне вызывают функцию для переопределения. Сейчас я просто заменяю МАКРОС, определенный в библиотеке, на МАКРОС, определенный в корневом проекте. Таким образом, я «заменяю» все вызовы malloc, когда библиотека компилируется для проекта. Мне просто интересно, существует ли лучший, передовой способ, который также останется эффективным.
Замена MACRO внутри библиотеки происходит только в том случае, если вы компилируете ее снова и снова. Если вы скомпилируете свою библиотеку один раз (и свяжете ее с помощью gcc ... -lfoo
), никакие изменения макросов в основном проекте не изменят поведение библиотеки.
Я бы сказал, что указатель на функцию является более эффективной практикой, поскольку, хорошо, вы перекомпилируете библиотеку с проектом X, но как насчет времени выполнения, когда поиск библиотеки может зависеть от PATH, а как насчет того, когда у вас есть несколько подбиблиотек или частей, использующих foo.dll : может применяться одно «переопределение» макроса (в зависимости от того, как вы его загружаете, но это библиотека, поэтому она должна работать). Мне кажется, что с указателями fn этого риска можно избежать, поэтому «лучшая практика»
По моему опыту, большинство библиотек, похоже, используют систему конфигурации на основе макросов, с возможностью для потребителей переопределить, определив указанный макрос самостоятельно. Шаблон кода имеет тенденцию быть следующим:
#if defined(LIB_CONFIG_HEADER)
#include LIB_CONFIG_HEADER
#else
// Include some reasonable defaults
#include <stdlib.h>
...
#endif
#if !defined(LIB_ALLOC)
#define LIB_ALLOC(_size) calloc(1u, _size)
#endif
Это имеет дополнительную гибкость, позволяя использовать в противном случае недопустимые выражения, такие как
#define LIB_ALLOC(_size) ...
#define LIB_ALLOC_T(_type) ((_type) *)LIB_ALLOC(sizeof(_type))
Поскольку они обрабатываются препроцессором, этот шаблон также может оказаться полезным для встроенных систем, где полная или совместимая стандартная библиотека недоступна.
Системы, которым требуется более сложный набор внедряемых поведений, склоняются к подходу «указатель функции/виртуальная таблица», который часто также включает методы распределения. Хотя этот подход действительно предполагает некоторую косвенность, на практике это не приводит к большим накладным расходам.
Есть еще третий вариант, предполагающий слабую связь, но я бы не рекомендовал его.
ваш путь, вероятно, самый быстрый. другой подход - создать собственную библиотеку, которая реализует/обертывает malloc (измените имя экспортированного символа) и поместите ее раньше в порядке связывания. возможно, потребуется добавить флаги, чтобы игнорировать множественные определения.