Дизайн реентерабельной библиотеки на С
Допустим, я создаю библиотеку, которая будет генерировать quuxes в C.
Quuxes нужно две переменные состояния для успешного появления:
static int quux_state;
static char* quux_address;
/* function to spork quuxes found in a file,
reads a line from the file each time it's called. */
void spork_quux(FILE*);
Если я сохраню эти данные как глобальные переменные, только один клиент сможет порождать quuxes одновременно, иначе переменные состояния будут искажены вторым вызывающим абонентом, и может произойти сбой.
Вопрос в том, что является лучшим способом создать реентерабельную библиотеку на C?
Я развлекал следующие случаи, без удовлетворительного заключения.
В следующем случае возникает вопрос, как связать клиента с каждым состоянием?
/* library handles all state data allocation */
static int* quux_state;
static char** quux_address;
В следующем случае клиент может связываться с государством, что очень нежелательно
/* let each client store state */
typedef struct { int state; char* address; } QuuxState;
QuuxState spork_quux(FILE*);
Итак, как это сделать правильно?
3 ответа
Используйте структуру, но не предоставляйте определение клиенту.
то есть. в заголовочный файл.h положите:
typedef struct QuuxState QuuxState;
QuuxState *spork_quux(FILE*);
и в файле реализации.c:
struct QuuxState
{
int state;
char* address;
};
QuuxState *spork_quuxFILE *f)
{
QuuxState *quuxState = calloc(1, sizeof(*quuxState));
if (!quuxState)
return NULL;
quuxState->state = ....;
........
return quuxState;
}
Преимущества этого подхода в том, что:
- вы можете изменить содержимое структуры без перекомпиляции всего клиентского кода
- клиентский код не может получить доступ к членам, т.е. quuxState->state выдаст ошибку компилятора
- структура QuuxState по-прежнему будет полностью видна отладчику, поэтому вы можете легко увидеть значения, установить точки наблюдения и т. д.
- не требуется кастинг
- тип, который вы возвращаете, является определенным типом, поэтому вы получите компилятор, проверяющий, что передается правильная вещь (по сравнению с указателем void *)
Единственным недостатком является то, что вам нужно выделить блок памяти - однако, если ваша библиотека делает что-то не тривиальное (а если она выполняет файловый ввод-вывод, это, конечно, нетривиально), то издержки одного malloc незначительны.
Возможно, вы захотите переименовать вышеупомянутую функцию во что-то вроде 'QuuxSpork_create' и добавить больше функций для выполнения построчной работы и разрушения состояния, как только вы закончите.
void QuuxSpork_readLine(QuuxState *state)
{
....
}
void QuuxSpork_destroy(QuuxState *state)
{
free(state);
}
Случайный пример библиотеки, которая работает примерно так, это библиотека потоков POSIX, pthreads.
Используйте структуру, но не говорите клиенту, что это структура. Раздайте непрозрачный указатель - void* или, что еще лучше, указатель на пустую фиктивную структуру - и приведите его обратно, когда это необходимо.
Способ, которым большинство библиотечных функций справляются с этим, состоит в том, чтобы возвращать информацию о состоянии пользователю в любом типе данных, который ему необходим. В вашем случае структура. (возьми стрток против стрток_р). Я считаю, что это создает прецедент, который вы должны передать обратно пользователю. Пустота * работает. Вы даже можете напечатать его так, чтобы он выглядел красиво.
Более того, strtok_r делает это, редактируя аргумент командной строки, не возвращая указатель на состояние. Я ожидаю, что любая повторяющаяся функция, которую я использую, будет следовать аналогичному формату. Конечно, мой мозг был искажен каким-то довольно сумасшедшим C-кодом.
void spork_quux(FILE*, QuuxState **);