# 2021q1 Homework2 (quiz2) contributed by <`YingMuo` > ###### tags: `linux2021` `quiz2` ## 進度 - [ ] 測驗一 - [X] 解釋程式碼 - [ ] 解釋效能 - [ ] 效能評比 - [ ] 測驗二 - [X] 解釋程式碼 - [X] linux kernal 中的原始碼 - [ ] slab - [ ] 測驗三 - [ ] 解釋程式碼並重寫 - [ ] linux kernal 中的原始碼 - [ ] 測驗四 - [X] 解釋程式碼 - [ ] 測試多執行緒 - [ ] chriso/intern ## 解釋程式運作原理 ### 結構 用 `next` 串成 singly linked list 並用 `list_head` 串成 circular doubly linked list ```c typedef struct __element { char *value; struct __element *next; struct list_head list; } list_ele_t; typedef struct { list_ele_t *head; /* Linked list of elements */ list_ele_t *tail; size_t size; struct list_head list; } queue_t; ``` 使用 `q_insert_head` 串起來的 linked list 會長這樣 ```graphviz graph insert_head{ node [shape = record] graph [rankdir = LR] head tail ele1 [label = "{<value> ele1|<next> next|<list_head> list}"] ele2 [label = "{<value> ele2|<next> next|<list_head> list}"] ele3 [label = "{<value> ele3|<next> next|<list_head> list}"] ele4 [label = "{<value> ele4|<next> next|<list_head> list}"] ele1:next -- NULL ele2:next -- ele1:value ele3:next -- ele2:value ele4:next -- ele3:value head -- ele4:value tail -- ele1:value list -- ele1:list_head ele1:list_head -- ele2:list_head ele2:list_head -- ele3:list_head ele3:list_head -- ele4:list_head ele4:list_head -- list } ``` 將 singly linked list 和 circular doubly linked list 同時出現會導致 list 結構難以想像,並且 singly linked list 能夠做到的操作 circular doubly linked list 都能夠做到,導致 singly linked list 顯得很冗贅 :::warning 能夠正確輸出預期結果的程式碼,不該用「沒意義」來描述 (你那些碩博班的學長姐連「輸出符合預期結果」的程式碼都做不到,這樣的高等教育才是更「沒意義」)。工程講究持續反省和進步的歷程,你指出冗余程式碼很好,但不要用「沒意義」來誤導讀者的理解。 寫共筆的其中一個作用是,預先對未來的面試和工作溝通做準備。 :notes: jserv ::: 而 `queue_t` 結構在沒有 `next` 之後也顯得一樣多餘了,剩下有用的 size 也沒有被任何函式用到,可以直接拿掉 修改過後的版本: ```c #include <assert.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "list.h" typedef struct __element { char *value; struct list_head list; } list_ele_t; static list_ele_t *get_middle(struct list_head *list) { struct list_head *fast = list->next, *slow; list_for_each(slow, list) { if (fast->next == list || fast->next->next == list) break; fast = fast->next->next; } return list_entry(slow, list_ele_t, list); } static void list_merge(struct list_head *lhs, struct list_head *rhs, struct list_head *head) { INIT_LIST_HEAD(head); if (list_empty(lhs)) { list_splice_tail(lhs, head); return; } if (list_empty(rhs)) { list_splice_tail(rhs, head); return; } while (!list_empty(lhs) && !list_empty(rhs)) { char *lv = list_entry(lhs->next, list_ele_t, list)->value; char *rv = list_entry(rhs->next, list_ele_t, list)->value; struct list_head *tmp = strcmp(lv, rv) <= 0 ? lhs->next : rhs->next; list_del(tmp); list_add_tail(tmp, head); } list_splice_tail(list_empty(lhs) ? rhs : lhs, head); } void list_merge_sort(struct list_head *q) { if (list_is_singular(q)) return; struct list_head left; struct list_head sorted; INIT_LIST_HEAD(&left); list_cut_position(&left, q, &get_middle(q)->list); list_merge_sort(&left); list_merge_sort(q); list_merge(&left, q, &sorted); INIT_LIST_HEAD(q); list_splice_tail(&sorted, q); } static bool validate(struct list_head *q) { struct list_head *node; list_for_each(node, q) { if (node->next == q) break; if (strcmp(list_entry(node, list_ele_t, list)->value, list_entry(node->next, list_ele_t, list)->value) > 0) return false; } return true; } static void q_free(struct list_head *q) { if (!q) return; list_ele_t *tmp = NULL; while (!list_empty(q)) { tmp = list_first_entry(q, list_ele_t, list); list_del(&tmp->list); free(tmp->value); free(tmp); } } bool q_insert_head(struct list_head *q, char *s) { if (!q) return false; list_ele_t *newh = malloc(sizeof(list_ele_t)); if (!newh) return false; char *new_value = strdup(s); if (!new_value) { free(newh); return false; } newh->value = new_value; list_add_tail(&newh->list, q); return true; } int main(void) { FILE *fp = fopen("cities.txt", "r"); if (!fp) { perror("failed to open cities.txt"); exit(EXIT_FAILURE); } struct list_head q; INIT_LIST_HEAD(&q); char buf[256]; while (fgets(buf, 256, fp)) q_insert_head(&q, buf); fclose(fp); list_merge_sort(&q); assert(validate(&q)); q_free(&q); return 0; } ``` 改完後的結構變成下圖,其實就是 `list_head` 的結構 ```graphviz graph insert_head{ node [shape = record] graph [rankdir = LR] q ele1 [label = "{<value> ele1|<list_head> list}"] ele2 [label = "{<value> ele2|<list_head> list}"] ele3 [label = "{<value> ele3|<list_head> list}"] ele4 [label = "{<value> ele4|<list_head> list}"] q -- ele1:list_head ele1:list_head -- ele2:list_head ele2:list_head -- ele3:list_head ele3:list_head -- ele4:list_head ele4:list_head -- q } ``` 修改前後記憶體空間使用比較 ```c $ valgrind --tool=memcheck --track-origins=yes ./list ==13737== HEAP SUMMARY: ==13737== in use at exit: 0 bytes in 0 blocks ==13737== total heap usage: 187,657 allocs, 187,657 frees, 5,280,092 bytes allocated ==13737== ==13737== All heap blocks were freed -- no leaks are possible ==13737== ==13737== For lists of detected and suppressed errors, rerun with: -s ==13737== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) $ valgrind --tool=memcheck --track-origins=yes ./list_nonext ==13746== HEAP SUMMARY: ==13746== in use at exit: 0 bytes in 0 blocks ==13746== total heap usage: 187,656 allocs, 187,656 frees, 4,529,436 bytes allocated ==13746== ==13746== All heap blocks were freed -- no leaks are possible ==13746== ==13746== For lists of detected and suppressed errors, rerun with: -s ==13746== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) ``` ### get_middle ```c static list_ele_t *get_middle(struct list_head *list) { struct list_head *fast = list->next, *slow; list_for_each(slow, list) { if (fast->next == list || fast->next->next == list) break; fast = fast->next->next; } return list_entry(slow, list_ele_t, list); } ``` 在這個函式中,`fast` 指標走訪串列的兩個節點,而 `slow` 指標則一次走訪串列的一個節點,所以當 `fast` 走到串列尾端時,`slow` 會在串列的中間部位,就可以拿到中間部位的節點。 ### list_merge ```c static void list_merge(struct list_head *lhs, struct list_head *rhs, struct list_head *head) { INIT_LIST_HEAD(head); if (list_empty(lhs)) { list_splice_tail(lhs, head); return; } if (list_empty(rhs)) { list_splice_tail(rhs, head); return; } while (!list_empty(lhs) && !list_empty(rhs)) { char *lv = list_entry(lhs->next, list_ele_t, list)->value; char *rv = list_entry(rhs->next, list_ele_t, list)->value; struct list_head *tmp = strcmp(lv, rv) <= 0 ? lhs->next : rhs->next; list_del(tmp); list_add_tail(tmp, head); } list_splice_tail(list_empty(lhs) ? rhs : lhs, head); } ``` 1. 如果其中一個 list 是空的,就直接把另一個 list 接在 head 後面 2. 兩條 list 的第一個 element 的值做比較,小於者會將第一個 element 從 list 中 remove 並加到 head 的尾端,直到其中一條 list 空的時候,就將另一條接在 head 後面 如果兩個 list 是 sorted,會得到一個也是 sorted 的 list ### list_cut_position ```c static inline void list_cut_position(struct list_head *head_to, struct list_head *head_from, struct list_head *node) { struct list_head *head_from_first = head_from->next; if (list_empty(head_from)) return; if (head_from == node) { INIT_LIST_HEAD(head_to); return; } head_from->next = node->next; head_from->next->prev = head_from; head_to->prev = node; node->next = head_to; head_to->next = head_from_first; head_to->next->prev = head_to; } ``` 會將 head_from 的第一個 element 到 node 都移到 head_to,剩下的都留在 head_from ### list_merge_sort ```c void list_merge_sort(queue_t *q) { if (list_is_singular(&q->list)) return; queue_t left; struct list_head sorted; INIT_LIST_HEAD(&left.list); list_cut_position(&left.list, &q->list, &get_middle(&q->list)->list); list_merge_sort(&left); list_merge_sort(q); list_merge(&left.list, &q->list, &sorted); INIT_LIST_HEAD(&q->list); list_splice_tail(&sorted, &q->list); } ``` 1. list 的第一個 element 到 get_middle 得到的 element 移到 left,分別進行 list_merge_sort,在 merge 到 sort ,並重新放到 list 2. 如果 list_is_singular 就會 return ,所以最小切到剩一個 element ```graphviz graph merge_sort { node [shape = record] subgraph cluster_00 { subgraph cluster_0 { "4" "3"; label = ""; color=blue; } subgraph cluster_1 { "2" "1"; label = ""; color=blue; } } subgraph cluster_01 { subgraph cluster_3 { "6" "5"; label = ""; color=blue; } subgraph cluster_2 { "8" "7"; label = ""; color=blue; } } } ``` ## list_sort ### merge ```c __attribute__((nonnull(2,3,4))) static struct list_head *merge(void *priv, cmp_func cmp, struct list_head *a, struct list_head *b) { struct list_head *head, **tail = &head; for (;;) { /* if equal, take 'a' -- important for sort stability */ if (cmp(priv, a, b) <= 0) { *tail = a; tail = &a->next; a = a->next; if (!a) { *tail = b; break; } } else { *tail = b; tail = &b->next; b = b->next; if (!b) { *tail = a; break; } } } return head; } ``` * ```__artribute__((nonnull(args)))``` 為第 args 的 parameter 不能是 NULL * 透過 cmp 去比較 a 和 b 的大小,比較小的會接在 head 的最後面 ( next ) 1. a < b, return < 0 2. a = b, return = 0 3. a > b, return > 0 * 如果其中一個 list 為空,就把另一個 list 接在 head 最後面 * 沒有 prev ( singly linked list ),沒有 circle ### merge_final ```c __attribute__((nonnull(2,3,4,5))) static void merge_final(void *priv, cmp_func cmp, struct list_head *head, struct list_head *a, struct list_head *b) { struct list_head *tail = head; u8 count = 0; for (;;) { /* if equal, take 'a' -- important for sort stability */ if (cmp(priv, a, b) <= 0) { tail->next = a; a->prev = tail; tail = a; a = a->next; if (!a) break; } else { tail->next = b; b->prev = tail; tail = b; b = b->next; if (!b) { b = a; break; } } } /* Finish linking remainder of list b on to tail */ tail->next = b; do { /* * If the merge is highly unbalanced (e.g. the input is * already sorted), this loop may run many iterations. * Continue callbacks to the client even though no * element comparison is needed, so the client's cmp() * routine can invoke cond_resched() periodically. */ if (unlikely(!++count)) cmp(priv, b, b); b->prev = tail; tail = b; b = b->next; } while (b); /* And the final links to make a circular doubly-linked list */ tail->next = head; head->prev = tail; } ``` * 透過 cmp 去比較 a 和 b 的大小,比較小的會接在 head 的最後面 ( 雙向 ) * 如果其中一個 list 為空,就把另一個 list 接在 head 最後面,並將其 prev 都補齊 * 把頭尾聯起來,形成 doubly circular linked list ### list_sort ```c __attribute__((nonnull(2, 3))) void list_sort(void *priv, struct list_head *head, int (*cmp)(void *priv, struct list_head *a, struct list_head *b)) { struct list_head *list = head->next, *pending = NULL; size_t count = 0; /* Count of pending */ if (list == head->prev) /* Zero or one elements */ return; /* Convert to a null-terminated singly-linked list. */ head->prev->next = NULL; /* * Data structure invariants: * - All lists are singly linked and null-terminated; prev * pointers are not maintained. * - pending is a prev-linked "list of lists" of sorted * sublists awaiting further merging. * - Each of the sorted sublists is power-of-two in size. * - Sublists are sorted by size and age, smallest & newest at front. * - There are zero to two sublists of each size. * - A pair of pending sublists are merged as soon as the number * of following pending elements equals their size (i.e. * each time count reaches an odd multiple of that size). * That ensures each later final merge will be at worst 2:1. * - Each round consists of: * - Merging the two sublists selected by the highest bit * which flips when count is incremented, and * - Adding an element from the input as a size-1 sublist. */ do { size_t bits; struct list_head **tail = &pending; /* Find the least-significant clear bit in count */ for (bits = count; bits & 1; bits >>= 1) tail = &(*tail)->prev; /* Do the indicated merge */ if (likely(bits)) { struct list_head *a = *tail, *b = a->prev; a = merge(priv, (cmp_func) cmp, b, a); /* Install the merged result in place of the inputs */ a->prev = b->prev; *tail = a; } /* Move one element from input list to pending */ list->prev = pending; pending = list; list = list->next; pending->next = NULL; count++; } while (list); /* End of input; merge together all the pending lists. */ list = pending; pending = pending->prev; for (;;) { struct list_head *next = pending->prev; if (!next) break; list = merge(priv, (cmp_func) cmp, pending, list); pending = next; } /* The final merge, rebuilding prev links */ merge_final(priv, (cmp_func) cmp, head, pending, list); } ``` * 將尾端的 next 指向 NULL ( 拆掉 circular ) * 將 list 的 prev 當作 pending 的空間來使用,每一個迴圈都會把 list 的頭放進 pending,形成前面是 pending 指向的 singly linked list 從後到前,後面是 list 指向的 singly linked list 從前到後,假設沒有進行 merge 的話會長的像下圖 ```graphviz digraph pending { node [shape = record] rankdir = LR subgraph cluster_pending { ele1 -> ele2 -> ele3 [dir=back] label = "pending" color = blue } head -> ele1 subgraph cluster_list { ele4 -> ele5 -> ele6 ele4 -> ele5 -> ele6 [dir = back] label = "list" color = blue } ele3 -> ele4 [dir = back] head -> ele6 pending -> ele3 list -> ele4 } ``` * count 的 least-significant 為 0 時會進行 merge,假設 ele4 < ele1 < ele5 < ele2 < ele6 < ele3 count = 3 ```graphviz digraph pending { node [shape = record] rankdir = LR subgraph cluster_pending { ele1 -> ele3 [dir=back] ele1 -> ele2 NULL -> ele1 [dir=back] label = "pending" color = blue } head -> ele1 subgraph cluster_list { ele4 -> ele5 -> ele6 ele4 -> ele5 -> ele6 [dir = back] label = "list" color = blue } ele3 -> ele4 [dir = back] head -> ele6 pending -> ele3 list -> ele4 } ``` count = 4 ```graphviz digraph pending { node [shape = record] rankdir = LR subgraph cluster_pending { ele1 -> ele4 [dir=back] ele1 -> ele2 ele4 -> ele3 NULL -> ele1 [dir=back] label = "pending" color = blue } head -> ele1 subgraph cluster_list { ele5 -> ele6 ele5 -> ele6 [dir = back] label = "list" color = blue } ele4 -> ele5 [dir = back] head -> ele6 pending -> ele4 list -> ele5 } ``` count = 5 ```graphviz digraph pending { node [shape = record] rankdir = LR subgraph cluster_pending { ele4 -> ele5 [dir=back] ele4 -> ele1 -> ele2 -> ele3 NULL -> ele4 [dir=back] label = "pending" color = blue } head -> ele1 subgraph cluster_list { ele6 label = "list" color = blue } ele5 -> ele6 [dir = back] head -> ele6 pending -> ele5 list -> ele6 } ``` count = 6,完美 2:1 ( ele4 有 4 個,ele6 有 2 個 ),bitwise 的奧秘 ```graphviz digraph pending { node [shape = record] rankdir = LR subgraph cluster_pending { ele4 -> ele6 [dir=back] ele6 -> ele5 ele4 -> ele1 -> ele2 -> ele3 NULL -> ele4 [dir=back] label = "pending" color = blue } head -> ele1 subgraph cluster_list { label = "list" color = blue } head -> ele6 pending -> ele6 NULL2 [label = "NULL"] list -> NULL2 } ``` * 如果在 pending 中的 linked list 大於 2 條會將其 merge 到只剩兩條 * 最後將剩下 2 條 list 進行 merge_final,除了會 merge 也會將 prev, circular 補回去,就完成了 merge sort ### 最佳化手法 TODO: :::warning 不要只是貼出超連結,你需要摘要並且指出其中不足之處。任何人都可以複製貼上,或者用一句話讚揚,但只有具備專業的人,才能看出細節。 現在就要求自己深究! :notes: jserv ::: --- ## power-of-2 ### 解釋函式 ```c uint16_t func(uint16_t N) { /* change all right side bits to 1 */ N |= N >> 1; N |= N >> 2; N |= N >> 4; N |= N >> 8; return (N + 1) >> 1; } ``` 以 1000 0000 0000 0000 為例 N = 1000 0000 0000 0000 N = 1100 0000 0000 0000 ( N |= N >> 1 ) N = 1111 0000 0000 0000 ( N |= N >> 2 ) N = 1111 1111 0000 0000 ( N |= N >> 4 ) N = 1111 1111 1111 1111 ( N |= N >> 8 ) 上述計算完後最高有效位將比他低位的 bit 都變成 1,因為他每次運算完都會將最高有效位及已經被他改變的位數都往後 shift,or 運算改到的範圍就會變大,所以能夠改變最高有效位後的所有 bit。 :::danger 改進上述漢語用法 :notes: jserv ::: ### round up/down power of in linux * __builtin_constant_p 會判斷 n 是不是 constant * ilog2 會得到 constant 的 log2 計算值 ```c #define roundup_pow_of_two(n) \ ( \ __builtin_constant_p(n) ? ( \ ((n) == 1) ? 1 : \ (1UL << (ilog2((n) - 1) + 1)) \ ) : \ __roundup_pow_of_two(n) \ ) ``` ```c static inline __attribute__((const)) unsigned long __roundup_pow_of_two(unsigned long n) { return 1UL << fls_long(n - 1); } ``` ```c #define rounddown_pow_of_two(n) \ ( \ __builtin_constant_p(n) ? ( \ (1UL << ilog2(n))) : \ __rounddown_pow_of_two(n) \ ) ``` ```c static inline __attribute__((const)) unsigned long __rounddown_pow_of_two(unsigned long n) { return 1UL << (fls_long(n) - 1); } ``` * \_\_roundup/down_pwo_of_two 都會呼叫 fls_long,而且從 roundup 為 fls_long(n-1) 與 rounddown 為 fls_long(n) - 1 可以猜測其就是將最高位以下的所以 bit 填成 1 ```c static inline unsigned fls_long(unsigned long l) { if (sizeof(l) == 4) return fls(l); return fls64(l); } ``` * 這裡就先只看 fls() 的行為 ```c static __always_inline int fls(unsigned int x) { int r = 32; if (!x) return 0; if (!(x & 0xffff0000u)) { x <<= 16; r -= 16; } if (!(x & 0xff000000u)) { x <<= 8; r -= 8; } if (!(x & 0xf0000000u)) { x <<= 4; r -= 4; } if (!(x & 0xc0000000u)) { x <<= 2; r -= 2; } if (!(x & 0x80000000u)) { x <<= 1; r -= 1; } return r; } ``` * 這裡跟測驗中的 code 其實是一樣的概念,只是反過來把位數扣掉而已 ## string intern ### 結構 #### cstring ```c typedef struct __cstr_data { char *cstr; uint32_t hash_size; uint16_t type; uint16_t ref; } * cstring; ``` string 結構的基本單位 * cstr - 字串 * hash_size - hash 值或是 string size * 在 interning - hash_blob 的返回值 ( hash值 ) * 使用 buffer 建構 ( 在 stack ) - string size * type - 不同的結構建構方式會有不同的 type * CSTR_PERMANENT - 使用 CSTR_LITERAL 建構或 cstr_grab 沒有 type 的 cstring 會賦予該 cstring 此 type * CSTR_INTERNING - 使用 cstr_interning 建構 * cstr_clone 在 sz < CSTR_INTERNING_SIZE 時使用 * cstr_cat 在 type 不為 CSTR_ONSTACK 且 sa + sb < CSTR_INTERNING_SIZE 時使用 * CSTR_ONSTACK - 使用 CSTR_BUFFER 建構 * 0 - 在 cstr_clone 或 cstr_cat 時沒有進到 interning 且不保持在 stack 上時 * ref - cstring 被多少東西依賴 * 在 hash table 或是在 stack 上的 cstring 為 0 * 在 cstr_clone 或 cstr_cat 時 size 超過 CSTR_INTERNING_SIZE 的 cstring 為 1 * 如果 type 不是有定義的 type 且 ref 不為 0 時 ( 方便稱呼這裡叫這情況為失去管理的 cstring ),cstr_grab 此 cstring 會讓 ref + 1 ( 監控失去管理的 cstring 被幾個東西依賴 ) 總結一下不同情況下對應的 hash_size、type 和 ref 的值 * 在 interning - hash_size = hash_blob, type = CSTR_INTERNING, ref = 0 * 使用 buffer 建構 ( 在 stack ) - hash_size = string size, type = CSTR_ONSTACK, ref = 0 * 未進行初始話的 cstring 在被 cstr_grab 後 - hash_size = 0, type = CSTR_PERMANENT, ref = 0 * 在 cstr_clone 或 cstr_cat 因為 string 過長而未進入 interning ( 失去管理的 cstring ) - hash_size = 0, type = 0, ref > 0 #### cstr_buffer ```c typedef struct __cstr_buffer { cstring str; } cstr_buffer[1]; ``` 將 cstring 包裝方便進行初始話 ```c struct __cstr_node { char buffer[CSTR_INTERNING_SIZE]; struct __cstr_data str; struct __cstr_node *next; }; ``` * hash table 的 node * buffer 給 cstr_data 的 cstr 有一個空間可以放字串 * linked list 可以改成 list_head #### cstr_pool ```c struct __cstr_pool { struct __cstr_node node[INTERNING_POOL_SIZE]; }; ``` interning 存放 cstr_node 的空間 #### cstr_interning ```c struct __cstr_interning { int lock; int index; unsigned size; unsigned total; struct __cstr_node **hash; struct __cstr_pool *pool; }; ``` 整個 interning 結構 * lock - 為了 atomic 的 lock 操作 * index - 紀錄 pool 使用到哪個 node * size - hash table 的 size * total - 在 interning 的 node 總數 * hash - hash table * pool - cstr_pool ### 函式 #### CSTR_LOCK/UNLOCK 使用 atomic 來做 lock 和 unlock #### static inline void insert_node(struct __cstr_node **hash, int sz, struct __cstr_node *node) * 在 hash table expand size 後把 node 重新放回 hash table 時使用 * 將 cstring 加到 ( hash_size % sz ) 那個 index 的 linked list 的 head #### static void expand(struct __cstr_interning *si) * 讓 hash table 的 size 變成原本的 2 倍 ( hash table 的 size 必須是 2 的 次方 ) * 重新開一個空間給 hash table * 將在舊的 hash table 裡的 node 加到新的 hash table 並 free 舊的 hash table #### static cstring interning(struct __cstr_interning *si, const char *cstr, size_t sz, uint32_t hash) * cstr_interning 的底層操作 * 如果在 hash table 中找到一個 hash_size 與輸入的字串一樣但 cstr 卻不同的 cstring 就會將該 cstring 回傳 * 為了讓 hash table 不那容易碰撞,如果 si->total * 5 >= si->size * 4 就回傳 NULL ( 4/5 threshold ) * 在 pool 中取出一個 node,配置好 cstring,放到 hash[hash_size % table 的 size] **threshold 的 4/5 是否為最佳值?** #### static cstring cstr_interning(const char *cstr, size_t sz, uint32_t hash) * 先上 lock * 在 lock 期間會去執行 interning,如果回傳值是 NULL 代表 hash table 不夠大就去執行 expand 並在執行 interning * 關閉 lock #### static inline uint32_t hash_blob(const char *buffer, size_t len) 對應 hash table 的 hash 值 #### cstring cstr_clone(const char *cstr, size_t sz) * * 如果 string size < CSTR_INTERNING_SIZE 就使用 cstr_interning 在 interning 裡註冊一個 string 並回傳 * 否則 malloc 一個空間 maintain 一個 cstring 並回傳( 失去管理的 cstring ) #### cstring cstr_grab(cstring s) * 依照 s 不同的 type 有不同操作 * type = CSTR_PERMANENT 或 CSTR_INTERNING 會直接回傳 s * type = CSTR_ONSTACK 會執行 cstr_clone 並回傳 clone 出來的 cstring ( 用 buffer 建構的 cstring 會得到其 clone ) * 如果 type 不是以上三個且 ref = 0 則 type = CSTR_PERMANENT 並回傳 s ( 推測是為了未初始化的 cstring ) * 如果皆不是上述情況則 s 的 ref + 1 並回傳 s ( 讓失去管理的 cstring 的依賴數 + 1 ) #### void cstr_release(cstring s) * 如果 s 的 type = 0 且 ref > 0 ( 失去管理的 cstring ) 就 s 的 ref - 1 ( 依賴數 - 1 ) * 如果剩下的依賴數是 0 就 free s #### static size_t cstr_hash(cstring s) * 為了不再重複計算 hash_blob * 如果 s 在 interning 內就回傳其 hash_size * 否則回傳 s 的 hash_blob 回傳值 #### int cstr_equal(cstring a, cstring b) * 如果 a 跟 b 是同一個指標回傳 true * 如果 a 跟 b 的 type 都是 CSTR_INTERNING 回傳 false * 如果 a 跟 b 的 type 都是 CSTR_ONSTACK 且 hash_size 一樣回傳 cstr 的 strcmp,否則 false * 不為上述情況的話會去比較 a 和 b 的 cstr_hash 值,如果一樣的話會回傳 cstr 的 strcmp #### static cstring cstr_cat2(const char *a, const char *b) * 如果 a + b 的 size , CSTR_INTERNING_SIZE 就把 a 和 b 依序 memcpy 進一個 char \[\] 然後使用 cstr_interning 放進 interning * 否則malloc 一個空間 maintain 一個 cstring 並回傳( 失去管理的 cstring ) #### cstring cstr_cat(cstr_buffer sb, const char *str) * 如果 sb->str->type 為 CSTR_ONSTACK 就將 str 直接接在 sb->str->cstr 後面 * 如果 str 都放進去就結束並回傳 sb->str->cstr,否則在 size = CSTR_STACK_SIZE 後就會將 sb->str->cstr 和 str 丟進 cstr_cat2 再回傳 sb->str->cstr