Повторно используемый дизайн библиотеки в C

Скажем, я создаю библиотеку к ложка-вилке quuxes в C.

Quuxes нужны переменные с двумя состояниями, чтобы быть sporked успешно:

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*);

Так, как сделать это правильно?

6
задан JosephH 13 July 2010 в 19:45
поделиться

3 ответа

Используйте struct, но не раскрывайте его определение клиенту.

т.е. в заголовочном файле .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. вы можете изменять содержимое struct без перекомпиляции всего клиентского кода
  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.

21
ответ дан 8 December 2019 в 04:28
поделиться

Используйте структуру, но не говорите клиенту, что это структура. Передавайте непрозрачный указатель - void*, а еще лучше указатель на пустой фиктивный struct - и возвращайте его обратно, когда это необходимо.

3
ответ дан 8 December 2019 в 04:28
поделиться

Большинство библиотечных функций обрабатывают это, возвращая пользователю информацию о состоянии в том типе данных, который ему нужен. В вашем случае struct. (возьмите strtok vs strtok_r). Я считаю, что это создает прецедент, когда вы должны передать его обратно пользователю. Пустота * работает. вы даже можете ввести его, чтобы он выглядел красиво.

Более того, strtok_r делает это, редактируя аргумент командной строки, не возвращая указатель на состояние. Я ожидаю, что любая функция повторного входа, которую я использую, будет следовать аналогичному формату. Конечно, мой мозг был искажен каким-то довольно сумасшедшим кодом C.

void spork_quux(FILE*, QuuxState **);
1
ответ дан 8 December 2019 в 04:28
поделиться
Другие вопросы по тегам:

Похожие вопросы: