├── .gitignore ├── test-macro1.c ├── dl-void.h ├── test-macro2.c ├── test-void.c ├── Makefile ├── test-intru1.c ├── dl-intru2.c ├── test-intru2.c ├── dl-intru1.h ├── dl-void.c ├── dl-intru2.h ├── dl-macro2.h ├── dl-macro1.h └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | .*.swp 3 | *.dSYM 4 | -------------------------------------------------------------------------------- /test-macro1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "dl-macro1.h" 4 | DL_IMPL(my, static inline, int) 5 | 6 | int main(void) 7 | { 8 | int i, N = 5; 9 | dl_list_t(my) *list; 10 | list = dl_init(my); 11 | for (i = 0; i < N; ++i) 12 | dl_push(my, list, i, 1); 13 | while (!dl_empty(my, list)) { 14 | int data; 15 | dl_pop(my, list, &data, 0); 16 | printf("out: %d\n", data); 17 | } 18 | dl_destroy(my, list); 19 | return 0; 20 | } 21 | -------------------------------------------------------------------------------- /dl-void.h: -------------------------------------------------------------------------------- 1 | #ifndef DL_VOID_H 2 | #define DL_VOID_H 3 | 4 | struct dl_list_s; // opaque struct 5 | typedef struct dl_list_s dl_list_t; 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | dl_list_t *dl_init(void); 12 | void dl_destroy(dl_list_t *list); 13 | int dl_empty(const dl_list_t *list); 14 | void dl_push(dl_list_t *list, void *data, int dir); 15 | void *dl_pop(dl_list_t *list, int dir); 16 | 17 | #ifdef __cplusplus 18 | } 19 | #endif 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /test-macro2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define DL_TYPE int 5 | #define DL_NAME my 6 | #include "dl-macro2.h" 7 | #undef DL_TYPE 8 | #undef DL_NAME 9 | 10 | int main(void) 11 | { 12 | int i, N = 5; 13 | DL_LIST_T(my) *list; 14 | list = DL_INIT(my)(); 15 | for (i = 0; i < N; ++i) 16 | DL_PUSH(my)(list, i, 1); 17 | while (!DL_EMPTY(my)(list)) { 18 | int data; 19 | DL_POP(my)(list, &data, 0); 20 | printf("out: %d\n", data); 21 | } 22 | DL_DESTROY(my)(list); 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /test-void.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "dl-void.h" 4 | 5 | #define MALLOC(type, len) (type*)malloc((len) * sizeof(type)) 6 | 7 | int main(void) 8 | { 9 | int i, N = 5; 10 | dl_list_t *list; 11 | list = dl_init(); 12 | for (i = 0; i < N; ++i) { 13 | int *x = MALLOC(int, 1); 14 | *x = i; 15 | dl_push(list, x, 1); 16 | } 17 | while (!dl_empty(list)) { 18 | void *p = dl_pop(list, 0); 19 | printf("out: %d\n", *(int*)p); 20 | free(p); 21 | } 22 | dl_destroy(list); 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | EXE=test-intru1 test-intru2 test-macro1 test-macro2 test-void 2 | 3 | .PHONY:all clean 4 | 5 | all:$(EXE) 6 | 7 | test-intru1:test-intru1.c dl-intru1.h 8 | $(CC) -Wall -g -O2 -o $@ $< 9 | 10 | test-intru2:test-intru2.c dl-intru2.c dl-intru2.h 11 | $(CC) -Wall -g -O2 -o $@ test-intru2.c dl-intru2.c 12 | 13 | test-macro1:test-macro1.c dl-macro1.h 14 | $(CC) -Wall -g -O2 -o $@ $< 15 | 16 | test-macro2:test-macro2.c dl-macro2.h 17 | $(CC) -Wall -g -O2 -o $@ $< 18 | 19 | test-void:test-void.c dl-void.c dl-void.h 20 | $(CC) -Wall -g -O2 -o $@ test-void.c dl-void.c 21 | 22 | clean: 23 | rm -fr a.out $(EXE) *.dSYM 24 | -------------------------------------------------------------------------------- /test-intru1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "dl-intru1.h" 4 | 5 | #define MALLOC(type, len) (type*)malloc((len) * sizeof(type)) 6 | 7 | typedef struct my_dlist_s { 8 | int x; 9 | DL_HEAD(struct my_dlist_s) head; 10 | } my_elem_t; 11 | 12 | DL_IMPL(my, my_elem_t, head) 13 | 14 | int main(void) 15 | { 16 | int i, N = 5; 17 | my_elem_t *head[2] = {0, 0}; 18 | for (i = 0; i < N; ++i) { 19 | my_elem_t *p = MALLOC(my_elem_t, 1); 20 | p->x = i; 21 | dl_push(my, head, p, 1); // push back 22 | } 23 | while (head[0]) { 24 | my_elem_t *p; 25 | p = dl_pop(my, head, 0); // pop front 26 | printf("out: %d\n", p->x); 27 | free(p); 28 | } 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /dl-intru2.c: -------------------------------------------------------------------------------- 1 | #include "dl-intru2.h" 2 | 3 | void dl_push(dl_head_t *head[2], dl_head_t *p, int dir) 4 | { 5 | dir = !!dir; // make sure dir is either 0 or 1 6 | p->p[0] = p->p[1] = 0; 7 | if (head[0] == 0 && head[1] == 0) head[0] = head[1] = p; // an empty list; then just add 8 | else head[dir]->p[dir] = p, p->p[!dir] = head[dir], head[dir] = p; // push 9 | } 10 | 11 | dl_head_t *dl_pop(dl_head_t *head[2], int dir) 12 | { 13 | dl_head_t *p; 14 | dir = !!dir; 15 | if (head[0] == 0 && head[1] == 0) return 0; // an empty list; return a NULL pointer 16 | else if (head[0] == head[1]) p = head[0], head[0] = head[1] = 0; // only one element; clear the list 17 | else p = head[dir], head[dir] = p->p[!dir], head[dir]->p[dir] = 0; // more than one elements 18 | return p; 19 | } 20 | -------------------------------------------------------------------------------- /test-intru2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "dl-intru2.h" 4 | 5 | #define MALLOC(type, len) (type*)malloc((len) * sizeof(type)) 6 | 7 | typedef struct { 8 | int x; 9 | dl_head_t head; // this field forms a double-linked list 10 | } my_elem_t; 11 | 12 | int main(void) 13 | { 14 | int i, N = 5; 15 | dl_head_t *head[2] = {0, 0}; 16 | for (i = 0; i < N; ++i) { 17 | my_elem_t *p = MALLOC(my_elem_t, 1); // caller controls all memory allocations 18 | p->x = i; 19 | dl_push(head, &p->head, 1); // push_back() 20 | } 21 | while (head[0]) { 22 | dl_head_t *p = dl_pop(head, 0); // pop_front() 23 | my_elem_t *q = dl_container_of(p, my_elem_t, head); // pointer to the parent struct 24 | printf("out: %d\n", q->x); 25 | free(q); // again, caller controls memory 26 | } 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /dl-intru1.h: -------------------------------------------------------------------------------- 1 | #ifndef DL_INTRU1_H 2 | #define DL_INTRU1_H 3 | 4 | // see dl-intru2.* for description of the APIs and the implementation 5 | 6 | #define DL_HEAD(_type) struct { _type *p[2]; } 7 | 8 | #define DL_IMPL(suf, _type, _head) /* suf creates a name space for a specific type to avoid naming clash */ \ 9 | static inline void dl_push_##suf(_type *head[2], _type *p, int dir) { /* ##suf for token concatenation */ \ 10 | dir = !!dir; /* 0 or 1 */ \ 11 | p->_head.p[0] = p->_head.p[1] = 0; \ 12 | if (head[0] == 0 && head[1] == 0) head[0] = head[1] = p; \ 13 | else head[dir]->_head.p[dir] = p, p->_head.p[!dir] = head[dir], head[dir] = p; \ 14 | } \ 15 | static inline _type *dl_pop_##suf(_type *head[2], int dir) { \ 16 | _type *p; \ 17 | dir = !!dir; \ 18 | if (head[0] == 0 && head[1] == 0) return 0; \ 19 | else if (head[0] == head[1]) p = head[0], head[0] = head[1] = 0; \ 20 | else p = head[dir], head[dir] = p->_head.p[!dir], head[dir]->_head.p[dir] = 0; \ 21 | return p; \ 22 | } 23 | 24 | // more convenient macro APIs 25 | #define dl_push(suf, head, p, dir) dl_push_##suf(head, p, dir) 26 | #define dl_pop(suf, head, dir) dl_pop_##suf(head, dir) 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /dl-void.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "dl-void.h" 3 | 4 | // see dl-intru2.* for the algorithm 5 | 6 | #define CALLOC(type, len) (type*)calloc((len), sizeof(type)) 7 | 8 | typedef struct dl_node_s { 9 | void *data; 10 | struct dl_node_s *p[2]; 11 | } dl_node_t; 12 | 13 | struct dl_list_s { 14 | dl_node_t *head[2]; 15 | }; 16 | 17 | dl_list_t *dl_init(void) { return CALLOC(dl_list_t, 1); } 18 | 19 | void dl_push(dl_list_t *list, void *data, int dir) 20 | { 21 | dl_node_t *p = CALLOC(dl_node_t, 1); 22 | dir = !!dir; 23 | p->data = data; 24 | if (list->head[0] == 0 && list->head[1] == 0) list->head[0] = list->head[1] = p; 25 | else list->head[dir]->p[dir] = p, p->p[!dir] = list->head[dir], list->head[dir] = p; 26 | } 27 | 28 | void *dl_pop(dl_list_t *list, int dir) 29 | { 30 | dl_node_t *p; 31 | void *data = 0; 32 | dir = !!dir; 33 | if (list->head[0] == 0 && list->head[1] == 0) return 0; 34 | else if (list->head[0] == list->head[1]) p = list->head[0], list->head[0] = list->head[1] = 0; 35 | else p = list->head[dir], list->head[dir] = p->p[!dir], list->head[dir]->p[dir] = 0; 36 | data = p->data; 37 | free(p); 38 | return data; 39 | } 40 | 41 | void dl_destroy(dl_list_t *list) 42 | { 43 | while (list->head[0]) { 44 | dl_node_t *p = dl_pop(list, 1); 45 | free(p); 46 | } 47 | free(list); 48 | } 49 | 50 | int dl_empty(const dl_list_t *list) 51 | { 52 | return (list == 0 || list->head[0] == 0); 53 | } 54 | -------------------------------------------------------------------------------- /dl-intru2.h: -------------------------------------------------------------------------------- 1 | #ifndef DL_INTRU2_H 2 | #define DL_INTRU2_H 3 | 4 | #include // for offsetof() 5 | 6 | typedef struct dl_head_s { // this struct can't be hidden 7 | struct dl_head_s *p[2]; // p[0] points the previous record; p[1] points to the next 8 | } dl_head_t; 9 | 10 | /** 11 | * Given a pointer to a struct member, get the pointer to the struct 12 | * 13 | * @param ptr pointer to a struct member 14 | * @param type type of the struct 15 | * @param member name of the member in the struct 16 | * 17 | * @return pointer to the struct 18 | */ 19 | #define dl_container_of(ptr, type, member) ((type *)((char *)(ptr) - offsetof(type, member))) 20 | 21 | #ifdef __cplusplus 22 | extern "C" { 23 | #endif 24 | 25 | /** 26 | * Push a record to the list 27 | * 28 | * A double-linked list is represented by two pointers: a head and a tail. For 29 | * an empty list, both of them are set to NULL pointers. As such, head[0] and 30 | * head[1] shall be set to NULL on the first call to this function. dl_push() 31 | * implements C++'s push_back() and push_front() at the same time. 32 | * 33 | * @param head head and tail of the list 34 | * @param p pointer to the element to add 35 | * @param dir direction: 0 for pushing from the front; 1 from the back 36 | */ 37 | void dl_push(dl_head_t *head[2], dl_head_t *p, int dir); 38 | 39 | /** 40 | * Pop a record from the list 41 | * 42 | * @param head head and tail of the list 43 | * @param dir direction 44 | * 45 | * @return pointer to the record, which is removed from the list. If the list 46 | * is empty, NULL is returned. If the list only has one record, head[0] and 47 | * head[1] are set to NULL, indicating an empty list. 48 | */ 49 | dl_head_t *dl_pop(dl_head_t *head[2], int dir); 50 | 51 | #ifdef __cplusplus 52 | } 53 | #endif 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /dl-macro2.h: -------------------------------------------------------------------------------- 1 | // no "#ifndef" guard as this header may be included multiple times 2 | 3 | // see dl-intru2.* for details on the methodology 4 | 5 | #define DL_CALLOC(type, len) (type*)calloc((len), sizeof(type)) 6 | 7 | #define DL_TOKCAT(pre, suf) pre ##_## suf 8 | 9 | #define DL_NODE_S(suf) DL_TOKCAT(dl_node_s, suf) 10 | #define DL_NODE_T(suf) DL_TOKCAT(dl_node_t, suf) 11 | #define DL_LIST_T(suf) DL_TOKCAT(dl_list_t, suf) 12 | 13 | #define DL_INIT(suf) DL_TOKCAT(dl_init, suf) 14 | #define DL_PUSH(suf) DL_TOKCAT(dl_push, suf) 15 | #define DL_POP(suf) DL_TOKCAT(dl_pop, suf) 16 | #define DL_EMPTY(suf) DL_TOKCAT(dl_empty, suf) 17 | #define DL_DESTROY(suf) DL_TOKCAT(dl_destroy, suf) 18 | 19 | typedef struct DL_NODE_S(DL_NAME) { 20 | DL_TYPE data; 21 | struct DL_NODE_S(DL_NAME) *p[2]; 22 | } DL_NODE_T(DL_NAME); 23 | 24 | typedef struct { 25 | DL_NODE_T(DL_NAME) *head[2]; 26 | } DL_LIST_T(DL_NAME); 27 | 28 | DL_LIST_T(DL_NAME) *DL_INIT(DL_NAME)(void) 29 | { 30 | return DL_CALLOC(DL_LIST_T(DL_NAME), 1); 31 | } 32 | 33 | void DL_PUSH(DL_NAME)(DL_LIST_T(DL_NAME) *list, DL_TYPE data, int dir) 34 | { 35 | DL_NODE_T(DL_NAME) *p = DL_CALLOC(DL_NODE_T(DL_NAME), 1); 36 | dir = !!dir; 37 | p->data = data; 38 | if (list->head[0] == 0 && list->head[1] == 0) list->head[0] = list->head[1] = p; 39 | else list->head[dir]->p[dir] = p, p->p[!dir] = list->head[dir], list->head[dir] = p; 40 | } 41 | 42 | int DL_POP(DL_NAME)(DL_LIST_T(DL_NAME) *list, DL_TYPE *data, int dir) 43 | { 44 | DL_NODE_T(DL_NAME) *p; 45 | dir = !!dir; 46 | if (list->head[0] == 0 && list->head[1] == 0) return 0; 47 | else if (list->head[0] == list->head[1]) p = list->head[0], list->head[0] = list->head[1] = 0; 48 | else p = list->head[dir], list->head[dir] = p->p[!dir], list->head[dir]->p[dir] = 0; 49 | *data = p->data; 50 | free(p); 51 | return 1; 52 | } 53 | 54 | int DL_EMPTY(DL_NAME)(const DL_LIST_T(DL_NAME) *list) 55 | { 56 | return (list == 0 || list->head[0] == 0); 57 | } 58 | 59 | void DL_DESTROY(DL_NAME)(DL_LIST_T(DL_NAME) *list) 60 | { 61 | DL_NODE_T(DL_NAME) *q, *p = list->head[0]; 62 | while (p) { q = p->p[1]; free(p); p = q; } 63 | free(list); 64 | } 65 | -------------------------------------------------------------------------------- /dl-macro1.h: -------------------------------------------------------------------------------- 1 | #ifndef DL_MACRO1_H 2 | #define DL_MACRO1_H 3 | 4 | // see dl-intru2.* for details on the methodology 5 | 6 | #define DL_CALLOC(type, len) (type*)calloc((len), sizeof(type)) 7 | 8 | #define DL_IMPL(name, SCOPE, _type) /* name creates a name space for a specific data type to avoid naming clash */ \ 9 | typedef struct dl_node_##name##_s { /* ##name## for token concatenation */ \ 10 | _type data; \ 11 | struct dl_node_##name##_s *p[2]; \ 12 | } dl_node_##name##_t; \ 13 | typedef struct { dl_node_##name##_t *head[2]; } dl_list_##name##_t; \ 14 | SCOPE dl_list_##name##_t *dl_init_##name(void) { return DL_CALLOC(dl_list_##name##_t, 1); } \ 15 | SCOPE void dl_push_##name(dl_list_##name##_t *list, _type data, int dir) { \ 16 | dl_node_##name##_t *p = DL_CALLOC(dl_node_##name##_t, 1); \ 17 | dir = !!dir; \ 18 | p->data = data; \ 19 | if (list->head[0] == 0 && list->head[1] == 0) list->head[0] = list->head[1] = p; \ 20 | else list->head[dir]->p[dir] = p, p->p[!dir] = list->head[dir], list->head[dir] = p; \ 21 | } \ 22 | SCOPE int dl_pop_##name(dl_list_##name##_t *list, _type *data, int dir) { /* *data is only set if 1 is returned */ \ 23 | dl_node_##name##_t *p; \ 24 | dir = !!dir; \ 25 | if (list->head[0] == 0 && list->head[1] == 0) return 0; \ 26 | else if (list->head[0] == list->head[1]) p = list->head[0], list->head[0] = list->head[1] = 0; \ 27 | else p = list->head[dir], list->head[dir] = p->p[!dir], list->head[dir]->p[dir] = 0; \ 28 | *data = p->data; \ 29 | free(p); \ 30 | return 1; \ 31 | } \ 32 | SCOPE void dl_destroy_##name(dl_list_##name##_t *list) { /* TODO: is this right? */ \ 33 | dl_node_##name##_t *q, *p = list->head[0]; \ 34 | while (p) { q = p->p[1]; free(p); p = q; } \ 35 | free(list); \ 36 | } \ 37 | SCOPE int dl_empty_##name(const dl_list_##name##_t *list) { return (list == 0 || list->head[0] == 0); } 38 | 39 | #define dl_list_t(name) dl_list_##name##_t 40 | 41 | #define dl_init(name) dl_init_##name() 42 | #define dl_destroy(name, list) dl_destroy_##name(list) 43 | #define dl_push(name, list, data, dir) dl_push_##name(list, data, dir) 44 | #define dl_pop(name, list, data, dir) dl_pop_##name(list, data, dir) 45 | #define dl_empty(name, list) dl_empty_##name(list) 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Generic Data Structures in C 2 | 3 | Unlike C++, the C programming language doesn't provide a general mechanism to 4 | implement generic data structures or algorithms. However, it is still possible 5 | to write generic C code matching the performance of type-specific 6 | implemntations. This repo demonstrates five ways to implement a generic 7 | double-linked list in C. Some of these techniques can be adapted to other data 8 | structures with no compromise on performance. 9 | 10 | 1. With `void*` pointers (`*-void.*`). This is the most common but often the 11 | least efficient way to implement generic data structures. It requires extra 12 | malloc per element, which wastes memory and hurts data locality. I don't recommend 13 | it. 14 | 15 | 2. With intrusive data structures (`*-intru2.*`). This approach doesn't call 16 | malloc/etc inside the library code, which gives callers full control of 17 | memory management. It is the preferred way to implement double-linked 18 | list, but is not optimal for binary trees because it incurs overhead to 19 | comparisons between objects. Generally, intrusive data structures are not 20 | applicable to vectors or [closed hash tables][closed-hash]. 21 | 22 | 3. Combining intrusive data structures and macros (`*-intru1.*`). This is my 23 | preferred way to implement binary trees (see [kavl.h][kavl]), but for 24 | double-linked list, it is more complex. 25 | 26 | 4. With macros (`*-macro1.*`). This approach can be optimal for all common data 27 | structures. However, it involves unusual syntax and is inflexible in terms of 28 | memory management at the caller end. My [khash.h][khash] library is 29 | implemented this way. STL containers also follow a similar rationale. 30 | 31 | 5. With before-header macros (`*-macro2.*`). This is an alternative way to 32 | implement method 4 and is functionally equivalent. It gets rid of long 33 | multi-line macros but complicates header inclusion. 34 | 35 | I recommend method 2 for double-linked lists, 3 for binary search trees such as 36 | red-black or AVL trees, and method 4 for plain vectors and closed hash tables. 37 | 38 | [closed-hash]: https://en.wikipedia.org/wiki/Open_addressing 39 | [khash]: https://github.com/attractivechaos/klib/blob/master/khash.h 40 | [kavl]: https://github.com/attractivechaos/klib/blob/master/kavl.h 41 | --------------------------------------------------------------------------------