Tuesday, August 31, 2010

[systemd notes] struct without definition

在读 systemd 的代码的时候,遇到如下一段代码,(systemd 的 src/fdset.h):
typedef struct FDSet FDSet;

FDSet* fdset_new(void);
void fdset_free(FDSet *s);

int fdset_put(FDSet *s, int fd);
int fdset_put_dup(FDSet *s, int fd);
问题是,这里的 FDSet 结构没有在任何地方定义。估计是 C 语言的某个特性 (具体是哪个,我就不知道了)。根据标准里的某条规则,未定义的 struct 可以拿来定义指针类型 (就像教科书上定义链表时的用法),而不会出现类型未定义的错误。比如下边这个小例子:
int main()
{
        struct foo *bar;
        return 0;
}
而如果把其中的 * 去掉,也就是把 bar 定义为 struct foo 类型,gcc 就会报错:
test.c:3: error: storage size of ‘bar’ isn’t known

进一步查看 systemd 代码会发现,FDSet 只用于定义指针,但从来没有进行实例化,没有一个变量 (或者说一块内存) 是声明为 FDSet 类型的。FDSet * 类型的指针所指向的内存都是通过 malloc 申请的,然后又被强制转换为 FDSet * 类型。所以编译器不关心 FDSet 本身有多大,而且也不关心这个类型究竟有没有定义。

利用这个特性,就能用 C 语言来实现一些有 OO 味道的数据结构了。除了上边提到的 FDSet, systemd 里还有 Set 和 Hashmap 这两个数据结构。FDSet 可以认为是 Set 的子类,而 Set 可以看作是 Hashmap 的子类。真正申请内存的操作都是在 hashmap.c 里面实现的,对内存的实际操作也是由 Hashmap 的函数来完成。对 FDSet 和 Set 类型的操作 (或者说对相应指针的操作) 都是对 Hashmap 操作的简单的封装。
#define MAKE_FDSET(s) ((FDSet*) s)

FDSet *fdset_new(void) {
        return MAKE_FDSET(set_new(trivial_hash_func, trivial_compare_func));
}

Set *set_new(hash_func_t hash_func, compare_func_t compare_func) {
        return MAKE_SET(hashmap_new(hash_func, compare_func));
}
Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func);