6

假设我正在构建一个库来在 C 中使用 quuxes。

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

如果我将该数据存储为全局变量,那么一次只有一个客户端能够触发 quux,否则状态变量将被第二个调用者破坏,并且可能会发生灾难。

问题是用 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*);

那么,如何正确地做到这一点呢?

4

3 回答 3

21

使用结构,但不要向客户端公开定义。

IE。在 .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* 指针相比)

唯一的缺点是您必须分配一块内存 - 但是假设您的库正在做任何不重要的事情(如果它正在执行文件 I/O,那肯定是不平凡的)单个 malloc 的开销可以忽略不计。

您可能希望将上述函数重命名为“QuuxSpork_create”之类的名称,并添加更多函数来处理执行逐行工作并在完成后破坏状态。

void QuuxSpork_readLine(QuuxState *state)
{
    ....
}

void QuuxSpork_destroy(QuuxState *state)
{
    free(state);
}

一个大致像这样工作的库的随机示例是 POSIX 线程库 pthreads。

于 2010-07-13T13:39:11.610 回答
3

使用结构,但不要告诉客户端它是结构。传递一个不透明的指针 - void*,或者更好的是指向一个空的虚拟结构的指针 - 并在需要时将其转换回。

于 2010-07-13T13:29:45.003 回答
1

大多数库函数处理此问题的方式是以用户需要的任何数据类型将状态信息返回给用户。在你的情况下,一个结构。(以 strtok 与 strtok_r 为例)。我相信这开创了一个先例,您应该将其传回给用户。一个 void * 有效。你甚至可以 typedef 让它看起来很漂亮。

此外,strtok_r 通过编辑命令行参数来实现这一点,而不是返回指向状态的指针。我希望我使用的任何重入函数都遵循类似的格式。当然,我的大脑已经被一些非常疯狂的 C 代码扭曲了。

void spork_quux(FILE*, QuuxState **);
于 2010-07-13T13:49:13.263 回答