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