Дизайн реентерабельной библиотеки на С

Допустим, я создаю библиотеку, которая будет генерировать 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;
}

Преимущества этого подхода в том, что:

  1. вы можете изменить содержимое структуры без перекомпиляции всего клиентского кода
  2. клиентский код не может получить доступ к членам, т.е. quuxState->state выдаст ошибку компилятора
  3. структура QuuxState по-прежнему будет полностью видна отладчику, поэтому вы можете легко увидеть значения, установить точки наблюдения и т. д.
  4. не требуется кастинг
  5. тип, который вы возвращаете, является определенным типом, поэтому вы получите компилятор, проверяющий, что передается правильная вещь (по сравнению с указателем 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 **);
Другие вопросы по тегам