我们可以利用 C11 6.7.2.1p15:
一个指向结构对象的指针,经过适当的转换,指向它的初始成员(或者如果该成员是位域,则指向它所在的单元),反之亦然。结构对象中可能有未命名的填充,但不是在其开头。
所以,假设你有
typedef struct linked_list linked_list;
struct linked_list {
struct linked_list *next;
};
所有链表类型都以linked_list common; 成员开头:
struct list1 {
linked_list common;
double value;
};
struct list2 {
linked_list common;
size_t size;
char data[];
};
struct list3 {
linked_list common;
/* ... other members ... */
};
对于 GCC,我们可以额外使用 __typeof__ 运算符将返回值设置为正确的类型;对于其他编译器,通用列表函数将返回 void *:
#define LIST_NONE ((void *)0)
#if defined(__gnuc__)
#define LIST_NEXT(list) ((__typeof__(list))(list_next(&((list)->common), (void *)(list))))
#define LIST_SIZE(list) (list_size(&((list)->common), (void *)(list)))
#define LIST_PUSH(list, item) (list_push((linked_list **)(list), &(*(list))->common, &((item)->common)))
#define LIST_POP(list) ((__typeof__(*(list)))list_pop((linked_list **)(list), &(*(list))->common))
#else
#define LIST_NEXT(list) ((void *)(list_next(&((list)->common), (void *)(list))))
#define LIST_SIZE(list) (list_size(&((list)->common), (void *)(list)))
#define LIST_PUSH(list, item) (list_push((linked_list **)(list), &(*(list))->common, &((item)->common)))
#define LIST_POP(list) ((void *)list_pop((linked_list **)(list), &(*(list))->common))
#endif
LIST_NONE 是一个 POSIX NULL 宏。 (也就是说,它在 POSIXy 机器上等价于NULL。)
LIST_SIZE() 和 LIST_NEXT() 宏将单个指针作为参数。该宏假定指针指向以linked_list common; 作为初始成员的结构类型,并将该成员的地址以及转换为void 指针的原始指针提供给相应的函数。基于 C11 6.7.2.1p15,两者应该相等。
如果指针指向的类型不是结构类型,或者结构类型缺少common 初始成员,或者该成员不是指向linked_list 的指针,编译器将发出不兼容的指针类型警告(如果启用)。
如果linked_list common; 不是结构中的初始成员,则提供给list_size() 或list_next() 函数的两个指针将不匹配。你可以使用例如assert() 来自 <assert.h> 检查:
linked_list *list_next(linked_list *list, void *copy)
{
assert((void *)list == copy);
return list->next;
}
size_t list_size(linked_list *list, void *copy)
{
size_t result = 0;
assert((void *)list == copy);
while (list) {
list = list->next;
result++;
}
return result;
}
如果你觉得不需要检查common成员是否在结构体的开头,可以省略assert()检查,函数调用中的第二个参数(包括宏中)完全一致。
LIST_PUSH() 和LIST_POP() 宏是相似的,但在这种情况下,我们需要一个指向链表成员指针的指针。我们没有尝试拼凑某种地址的地址运算符,而是将指针指针转换为指针指针到linked_list,但也提供common成员的地址用于类型检查:
void list_push(linked_list **listptr, linked_list *list, linked_list *item)
{
assert(list == *listptr);
item->next = *listptr;
*listptr = item;
}
linked_list *list_pop(linked_list **listptr, linked_list *list)
{
assert(list == *listptr);
if (*listptr) {
linked_list *result = *listptr;
*listptr = result->next;
result->next = (linked_list *)0;
return result;
} else
return (linked_list *)0;
}
在这种情况下,如果你觉得不需要验证common成员是否在链表结构类型的开头,可以省略assert()检查;但是,无论如何都不能省略list 参数,因为即使不使用它的值,编译器仍然会检查它的类型。
这是我用来验证上述内容的完整快速拼凑的测试程序:
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
typedef struct linked_list linked_list;
struct linked_list {
struct linked_list *next;
};
struct list1 {
linked_list common;
double value;
};
struct list2 {
linked_list common;
size_t size;
char data[];
};
struct list3 {
linked_list common;
/* ... other members ... */
};
static inline void list_push(linked_list **listptr, linked_list *list, linked_list *item)
{
assert(list == *listptr);
item->next = *listptr;
*listptr = item;
}
static inline linked_list *list_pop(linked_list **listptr, linked_list *list)
{
assert(list == *listptr);
if (*listptr) {
linked_list *result = *listptr;
*listptr = result->next;
result->next = (linked_list *)0;
return result;
} else
return (linked_list *)0;
}
static inline linked_list *list_next(linked_list *list, void *copy)
{
assert((void *)list == copy);
return list->next;
}
static inline size_t list_size(linked_list *list, void *copy)
{
size_t result = 0;
assert((void *)list == copy);
while (list) {
list = list->next;
result++;
}
return result;
}
#if defined(__gnuc__)
#define LIST_NEXT(list) ((__typeof__(list))(list_next(&((list)->common), (void *)(list))))
#define LIST_SIZE(list) (list_size(&((list)->common), (void *)(list)))
#define LIST_PUSH(list, item) (list_push((linked_list **)(list), &(*(list))->common, &((item)->common)))
#define LIST_POP(list) ((__typeof__(*(list)))list_pop((linked_list **)(list), &(*(list))->common))
#else
#define LIST_NEXT(list) ((void *)(list_next(&((list)->common), (void *)(list))))
#define LIST_SIZE(list) (list_size(&((list)->common), (void *)(list)))
#define LIST_PUSH(list, item) (list_push((linked_list **)(list), &(*(list))->common, &((item)->common)))
#define LIST_POP(list) ((void *)list_pop((linked_list **)(list), &(*(list))->common))
#endif
void test_list1(void)
{
struct list1 a, b, c, *root = NULL;
size_t n;
a.value = 1.0; LIST_PUSH(&root, &a);
b.value = 2.0; LIST_PUSH(&root, &b);
c.value = 3.0; LIST_PUSH(&root, &c);
n = LIST_SIZE(root);
if (n == 3)
printf("test_list1(): list_size() OK\n");
else
printf("test_list1(): list_size() == %zu, ERROR\n", n);
if (LIST_POP(&root) != &c)
printf("test_list1(): list_pop() ERROR");
else
if (LIST_POP(&root) != &b)
printf("test_list1(): list_pop() ERROR");
else
if (LIST_POP(&root) != &a)
printf("test_list1(): list_pop() ERROR");
else
if (root)
printf("test_list1(): list_pop() ERROR");
else
printf("test_list1(): list_pop() OK\n");
}
void test_list2(void)
{
struct list2 a, b, c, *root = NULL;
size_t n;
a.size = 1; LIST_PUSH(&root, &a);
b.size = 2; LIST_PUSH(&root, &b);
c.size = 3; LIST_PUSH(&root, &c);
n = LIST_SIZE(root);
if (n == 3)
printf("test_list2(): list_size() OK\n");
else
printf("test_list2(): list_size() == %zu, ERROR\n", n);
if (LIST_POP(&root) != &c)
printf("test_list2(): list_pop() ERROR");
else
if (LIST_POP(&root) != &b)
printf("test_list2(): list_pop() ERROR");
else
if (LIST_POP(&root) != &a)
printf("test_list2(): list_pop() ERROR");
else
if (root)
printf("test_list2(): list_pop() ERROR");
else
printf("test_list2(): list_pop() OK\n");
}
void test_list3(void)
{
struct list3 a, b, c, *root = NULL;
size_t n;
LIST_PUSH(&root, &a);
LIST_PUSH(&root, &b);
LIST_PUSH(&root, &c);
n = LIST_SIZE(root);
if (n == 3)
printf("test_list3(): list_size() OK\n");
else
printf("test_list3(): list_size() == %zu, ERROR\n", n);
if (LIST_POP(&root) != &c)
printf("test_list3(): list_pop() ERROR");
else
if (LIST_POP(&root) != &b)
printf("test_list3(): list_pop() ERROR");
else
if (LIST_POP(&root) != &a)
printf("test_list3(): list_pop() ERROR");
else
if (root)
printf("test_list3(): list_pop() ERROR");
else
printf("test_list3(): list_pop() OK\n");
}
int main(void)
{
test_list1();
test_list2();
test_list3();
return EXIT_SUCCESS;
}
请注意,由于我只使用这个单一编译单元中的函数,所以我将列表访问器函数标记为static inline。这应该让编译器优化掉额外的函数参数,并整体生成更好的代码。如果您在多个编译单元中使用这些函数,我可能只保留 static inline 并让编译器生成重复项,除非代码大小是一个问题。
由于我使用 GCC,我编译并运行了上面的 list.c 使用
gcc -Wall -std=c99 -pedantic list.c -o test && ./test
如果您发现上述错误,或者您认为我误解了 C11 标准的原因,请在 cmets 中告诉我,以便我进行验证。