# 2021q1 Homework2 (quiz2) contributed by < `ccs100203` > ###### tags: `linux2021` > [第 2 週測驗題](https://hackmd.io/@sysprog/linux2021-quiz2) ## 測驗 1 以下是針對 doubly-linked list 的 merge sort 實作: ```cpp #include <string.h> 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; 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(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); } ``` ### 解釋程式原理 > 參考 [Merge sort](https://en.wikipedia.org/wiki/Merge_sort) 程式會藉由 `get_middle` 取得該 list 的中間點,再透過 `list_cut_position` 做切割,然後重複的遞迴呼叫。當所有節點都分開後會經由 `list_merge` 將其排序以及結合。 #### `get_middle` 此函式的目的是找出 list 的中間點 ```cpp /** * list_for_each - iterate over list nodes * @node: list_head pointer used as iterator * @head: pointer to the head of the list */ #define list_for_each(node, head) \ for (node = (head)->next; node != (head); node = node->next) 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); } ``` 下面的圖表只繪製出 `next` 指標 - 首先,會分別用 `fast`、`slow` 指標指向 list 的前兩個 node ```graphviz digraph graphname{ node[shape=box] fast[shape=plaintext,fontcolor=red] slow[shape=plaintext,fontcolor=red] "*list"[shape=plaintext,fontcolor=red] rankdir=LR { rank="same"; slow->A "*list"->A } { fast->B } A->B->C->D->E->F->G->A } ``` - 再來進入了 `list_for_each` 所定義的迴圈內,一開始的賦值階段會先將 `slow` 指向下一個節點 ```graphviz digraph graphname{ node[shape=box] fast[shape=plaintext,fontcolor=red] slow[shape=plaintext,fontcolor=red] "*list"[shape=plaintext,fontcolor=red] rankdir=LR { rank="same" "*list"->A } { rank="same" fast->B slow->B } A->B->C->D->E->F->G->A } ``` - 在 if 的判斷通過之前,迴圈會讓 `slow` 不斷指向下一個節點,而 `fast` 則會指向下下個節點 ```graphviz digraph graphname{ node[shape=box] fast[shape=plaintext,fontcolor=red] slow[shape=plaintext,fontcolor=red] "*list"[shape=plaintext,fontcolor=red] rankdir=LR { rank="same" "*list"->A } { rank="same"; slow->C } { rank="same"; fast->D } A->B->C->D->E->F->G->A } ``` - 此時,`fast->next->next == list` 的判斷式會成立 可以看出 `slow` 會位於 list 中間的節點,所以藉著 `slow` 就可以將 A、B、C 與 D、E、F、G 分開。 ```graphviz digraph graphname{ node[shape=box] fast[shape=plaintext,fontcolor=red] slow[shape=plaintext,fontcolor=red] "*list"[shape=plaintext,fontcolor=red] rankdir=LR { rank="same" "*list"->A } { rank="same"; slow->D } { rank="same"; fast->F } A->B->C->D->E->F->G->A } ``` #### `list_cut_position` 此函式用來將一條 list 從特定的點切分成兩條 list。 他會將 `head_from` 之後一直到 `node` 之間的節點接到 `head_to`。 舉例來說: ``` node | V HEAD->N1->N2->N3->N4->N5->N6->N7->N8 LEFT ``` 最終情況會變成下列兩條 list ``` HEAD->N5->N6->N7->N8 LEFT->N1->N2->N3->N4 ``` - 下面是程式碼以及更詳盡的圖解 ```cpp= /** * list_cut_position() - Move beginning of a list to another list * @head_to: pointer to the head of the list which receives nodes * @head_from: pointer to the head of the list * @node: pointer to the node in which defines the cutting point */ 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; } ``` - 給定剛進入函式時的情況 ```graphviz digraph graphname{ node[shape=box] head_to[shape=plaintext,fontcolor=red] head_from[shape=plaintext,fontcolor=red] "node"[shape=plaintext,fontcolor=red] rankdir=LRBT { rank="same" A->B->C->D [style=invis] } "node"->C head_from->A A->B->C->D D->C->B->A head_to->N->N:w N->N:e A->D:e D->A:w } ``` - 執行完 Line 21, 22 ```cpp=21 head_from->next = node->next; head_from->next->prev = head_from; ``` ```graphviz digraph graphname{ node[shape=box] head_to[shape=plaintext,fontcolor=red] head_from[shape=plaintext,fontcolor=red] "node"[shape=plaintext,fontcolor=red] rankdir=LRBT { rank="same" A->B->C->D [style=invis] } "node"->C head_from->A A->D:e B->C C->B B->A C->D->A:w A->D:e D->A:w head_to->N->N:w N->N:e } ``` - 執行完 Line 24, 25 ```cpp=24 head_to->prev = node; node->next = head_to; ``` ```graphviz digraph graphname{ node[shape=box] head_to[shape=plaintext,fontcolor=red] head_from[shape=plaintext,fontcolor=red] "node"[shape=plaintext,fontcolor=red] rankdir=LRBT { rank="same" A->B->C->D [style=invis] } "node"->C head_from->A A->D B->C C->B B->A C->N D->A A->D D->A head_to->N->C N->N:e } ``` - 執行完 Line 26, 27 ```cpp=26 head_to->next = head_from_first; head_to->next->prev = head_to; ``` ```graphviz digraph graphname{ node[shape=box] head_to[shape=plaintext,fontcolor=red] head_from[shape=plaintext,fontcolor=red] "node"[shape=plaintext,fontcolor=red] rankdir=LRBT { rank="same" A->B->C->D [style=invis] } "node"->C head_from->A A->D B->C C->B B->N C->N D->A A->D D->A head_to->N->C N->B } ``` --- ## 測驗 2 考慮函式 func 接受一個 16 位元無號整數 $N$,並回傳小於或等於 $N$ 的 power-of-2 (漢字可寫作為 2 的冪) ```cpp= 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; } ``` - 先考慮一個數字如何轉換為 power of 2,以一個隨意的 16 位元無號整數來說,只要保留最高位的 1 並將其他都轉換為 0 即可。 - 所以先將比最高位還低的位元全部轉換為 1,這樣就可以產生出 $00...0011...111$ 的數字,此時只需要將這個數字 + 1,再將他往右位移,就可以得到 power of 2。 - 以 1025 為例,可以看到透過前面的幾行會將較低位元的 bit 全數轉換為 1,再透過第八行將他轉換為 power of 2。 | after<br>execution | 15 <br>MSB | 14~11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 <br>LSB | | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | Line 2 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | | Line 3 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | | Line 4 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | | Line 5 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | | Line 6 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | Line 8 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ### 查閱 Linux 核心原始程式碼 power of 2 > 根據 [log2.h](https://elixir.bootlin.com/linux/latest/source/include/linux/log2.h#L36) #### is_power_of_2 ```cpp /** * is_power_of_2() - check if a value is a power of two * @n: the value to check * * Determine whether some value is a power of two, where zero is * *not* considered a power of two. * Return: true if @n is a power of 2, otherwise false. */ static inline __attribute__((const)) bool is_power_of_2(unsigned long n) { return (n != 0 && ((n & (n - 1)) == 0)); } ``` - 已知如果一個數字只有任一且唯一一個 bit 為 1,其餘為 0,那麼他就屬於 power of 2。 - `(n & (n - 1)` 的操作,其實就是將最低位的 bit 設為 0。 假設 `n` 為 $11000$, `n - 1` 就是 $10111$,此時做 and 運算就可以將最底位的 bit 設為 0。 也藉由這個特性,跟上述已知道的條件,所以在做完 `(n & (n - 1)` 後,若數字變為 0,則代表其為 power of 2。 #### `__roundup_pow_of_two` 和 `__rounddown_pow_of_two` 解析 `__roundup_pow_of_two` 是為了將數字無條件進位到比較大的 power of 2,反之則為 `__rounddown_pow_of_two`。 - 以 $24$ 舉例: - `__roundup_pow_of_two(24)` 應得到 32 - `__rounddown_pow_of_two(24)` 應得到 16 觀察以下程式碼,`fls_long` 的作用是 find leading set,也就是找出 bit 中最高位的 1。 - 已知 `fls_long(24)` 會得到 5 - `__roundup_pow_of_two` 只要再藉由 `1U << 5` 就能得到 32,那為什麼需要 `n-1` 呢? 假設今天不是 24,而是 32 這種屬於 power of 2 的數字時,程式是不需要做進位的,但是直接做 `fls_long(32)` 會得到 6,代表程式在不需要進位時還是會進位。所以需要藉由 `n-1` 去避免掉程式多做了進位的情況。 - `__rounddown_pow_of_two` 由於是要無條件捨去,所以可以很直觀的將 `fls_long(n)` 的結果 - 1,也就是只保留了原先數字中最高位為 1 的 bit。 ```cpp /** * __roundup_pow_of_two() - round up to nearest power of two * @n: value to round up */ static inline __attribute__((const)) unsigned long __roundup_pow_of_two(unsigned long n) { return 1UL << fls_long(n - 1); } /** * __rounddown_pow_of_two() - round down to nearest power of two * @n: value to round down */ static inline __attribute__((const)) unsigned long __rounddown_pow_of_two(unsigned long n) { return 1UL << (fls_long(n) - 1); } ``` #### 如何 find leading set 由於上述的程式用到了 find leading set 的功能,所以來解釋他的做法。首先可以看到 `fls_long` 只是藉由不同的 type size 去呼叫不同的 fls。 ##### fls_long ```cpp static inline unsigned fls_long(unsigned long l) { if (sizeof(l) == 4) return fls(l); return fls64(l); } ``` 這邊只擷取了某一部分的情況,可以看到 find leading set 是藉由 number of bits 減 count leading zero 做出來的,而 `__kernel_ctlz` 又會藉 GCC Built in function 裡的 `__builtin_clzl` 去實作。 透過這些函式的串聯,就可以知道 `fls_long` 的功用與作法。 ##### fls && fls64 ```cpp static inline int fls64(unsigned long word) { return 64 - __kernel_ctlz(word); } static inline int fls(unsigned int x) { return fls64(x); } ``` ##### `__kernel_ctlz(x)` ```cpp # define __kernel_ctlz(x) __builtin_clzl(x) ``` :::info - 參考 [fls_long](https://elixir.bootlin.com/linux/latest/source/include/linux/bitops.h#L185) - 參考 [fls](https://elixir.bootlin.com/linux/latest/source/arch/alpha/include/asm/bitops.h#L366) - 參考 [__kernel_ctlz](https://elixir.bootlin.com/linux/latest/source/arch/alpha/include/uapi/asm/compiler.h#L55) - 參考 [Other Built-in Functions Provided by GCC](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html) ::: #### `__attribute__((const))` 用處 > 參考 [Declaring Attributes of Functions](https://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Function-Attributes.html) Basically this is just slightly more strict class than the pure attribute below, since function is not allowed to read global memory. Note that a function that has pointer arguments and examines the data pointed to must not be declared const. Likewise, a function that calls a non-const function usually must not be const. It does not make sense for a const function to return void. 規定 function 不能夠含有指標相關的參數以及調動到 global memory,同時也要求其必須有 void 以外的回傳值。 :::warning 換言之,`__attribute__((const))` 讓 C function (函式) 限縮到數學意義上的 function (函數),這也是為何中文翻譯應該區分「函式」和「函數」 :notes: jserv ::: --- ## 測驗 3 以下是 `bitcpy` 實作,允許開發者對指定的記憶體區域,逐 bit 進行資料複製: (避免篇幅過長故隱藏) :::spoiler ```cpp= #include <stdint.h> void bitcpy(void *_dest, /* Address of the buffer to write to */ size_t _write, /* Bit offset to start writing to */ const void *_src, /* Address of the buffer to read from */ size_t _read, /* Bit offset to start reading from */ size_t count) { size_t read_lhs = _read & 7; size_t read_rhs = 8 - read_lhs; const uint8_t *source = (const uint8_t *) _src + (_read / 8); size_t write_lhs = _write & 7; size_t write_rhs = 8 - write_lhs; uint8_t *dest = (uint8_t *) _dest + (_write / 8); static const uint8_t read_mask[] = { 0x00, /* == 0 00000000b */ 0x80, /* == 1 10000000b */ 0xC0, /* == 2 11000000b */ 0xE0, /* == 3 11100000b */ 0xF0, /* == 4 11110000b */ 0xF8, /* == 5 11111000b */ 0xFC, /* == 6 11111100b */ 0xFE, /* == 7 11111110b */ 0xFF /* == 8 11111111b */ }; static const uint8_t write_mask[] = { 0xFF, /* == 0 11111111b */ 0x7F, /* == 1 01111111b */ 0x3F, /* == 2 00111111b */ 0x1F, /* == 3 00011111b */ 0x0F, /* == 4 00001111b */ 0x07, /* == 5 00000111b */ 0x03, /* == 6 00000011b */ 0x01, /* == 7 00000001b */ 0x00 /* == 8 00000000b */ }; while (count > 0) { uint8_t data = *source++; size_t bitsize = (count > 8) ? 8 : count; if (read_lhs > 0) { data <<= read_lhs; if (bitsize > read_rhs) data |= (*source >> read_rhs); } if (bitsize < 8) data &= read_mask[bitsize]; uint8_t original = *dest; uint8_t mask = read_mask[write_lhs]; if (bitsize > write_rhs) { /* Cross multiple bytes */ *dest++ = (original & mask) | (data >> write_lhs); original = *dest & write_mask[bitsize - write_rhs]; *dest = original | (data << write_rhs); } else { // Since write_lhs + bitsize is never >= 8, no out-of-bound access. mask |= write_mask[write_lhs + bitsize]; *dest++ = (original & mask) | (data >> write_lhs); } count -= bitsize; } } ``` ::: 透過 `read_lhs` 與 `read_rhs` 可以知道資料所需的偏移量。而 write 也是使用了相同的手段。 而 `_read / 8` 可以得知要從第幾個 byte 開始讀取。 ```cpp=9 size_t read_lhs = _read & 7; size_t read_rhs = 8 - read_lhs; const uint8_t *source = (const uint8_t *) _src + (_read / 8); size_t write_lhs = _write & 7; size_t write_rhs = 8 - write_lhs; uint8_t *dest = (uint8_t *) _dest + (_write / 8); ``` 限制每次最多處理 8 bits。 並且藉由 `bitsize` 與 `read_rhs` 的比較去判斷此次運算是否需要跨越到下一個 byte。 如果需要的話代表 `data` 需要加入下一個 byte 內最高位開始的 `read_rhs` 個 bit。 ```cpp=42 size_t bitsize = (count > 8) ? 8 : count; if (read_lhs > 0) { data <<= read_lhs; if (bitsize > read_rhs) data |= (*source >> read_rhs); } ``` 而 `read_mask` 與 `write_mask` 的用處就在於保證程式只更改 / 更新到所需要的 bits。 - 先解釋程式如何進行寫入: 如果需要寫入最低位的 3 個 bit,則會產生下列的 mask: ```graphviz digraph { rankdir=LR byte [shape="record", label="{1|1|1|1|1|0|0|0}"]; } ``` 再來利用 `*dest = (*dest & mask) | (data >> write_lhs);` `(*dest & mask)` 可以保留不須更改的高位 5 個 bits。 `(data >> write_lhs)` 則是用來取出需要寫入的 bits。 將兩者做 bitwise or 運算就可以完成預期中寫入的動作。 - 程式碼寫入實作說明 如果不需要跨位元組寫入,透過 `mask |= write_mask[write_lhs + bitsize];` 會將需要寫入的 bits 設為 0,而其餘設為 1,就可以透過下一行的操作在不更動其餘資料的狀況下將資料填入。 如果需要跨位元組寫入,第 60 行的會先寫入第一個 byte,利用 `mask` 將其操作限制在最右邊幾個所需的 bits。而下面兩行則是利用 `write_mask` 將需要寫入的幾個高位元的 bit 設為 0,其餘不變的則為 0,藉此完成寫入的操作。 ```cpp=53 if (bitsize < 8) data &= read_mask[bitsize]; uint8_t original = *dest; uint8_t mask = read_mask[write_lhs]; if (bitsize > write_rhs) { /* Cross multiple bytes */ *dest++ = (original & mask) | (data >> write_lhs); original = *dest & write_mask[bitsize - write_rhs]; *dest = original | (data << write_rhs); } else { // Since write_lhs + bitsize is never >= 8, no out-of-bound access. mask |= write_mask[write_lhs + bitsize]; *dest++ = (original & mask) | (data >> write_lhs); } ``` TODO 研究 linux kernel [bitmaps](https://github.com/torvalds/linux/blob/master/include/linux/types.h) --- ## 測驗 4 > 參考 [String interning](https://en.wikipedia.org/wiki/String_interning) > In computer science, string interning is a method of storing only one copy of each distinct string value, which must be immutable. Interning strings makes some string processing tasks more time- or space-efficient at the cost of requiring more time when the string is created or interned. > 以及 [Immutable object](https://en.wikipedia.org/wiki/Immutable_object) > In object-oriented and functional programming, an immutable object (unchangeable object) is an object whose state cannot be modified after it is created. String interning 是將出現多處的字串,只保存在單一的儲存空間,存取時直接讀取 reference,藉此節省運算與儲存成本。 以下是 string interning 在 C 語言中的簡易實作: (避免篇幅過長故隱藏) :::spoiler - cstr.h ```cpp= #pragma once #include <stddef.h> #include <stdint.h> #include <stdlib.h> enum { CSTR_PERMANENT = 1, CSTR_INTERNING = 2, CSTR_ONSTACK = 4, }; #define CSTR_INTERNING_SIZE (32) #define CSTR_STACK_SIZE (128) typedef struct __cstr_data { char *cstr; uint32_t hash_size; uint16_t type; uint16_t ref; } * cstring; typedef struct __cstr_buffer { cstring str; } cstr_buffer[1]; #define CSTR_S(s) ((s)->str) #define CSTR_BUFFER(var) \ char var##_cstring[CSTR_STACK_SIZE] = {0}; \ struct __cstr_data var##_cstr_data = {var##_cstring, 0, CSTR_ONSTACK, 0}; \ cstr_buffer var; \ var->str = &var##_cstr_data; #define CSTR_LITERAL(var, cstr) \ static cstring var = NULL; \ if (!var) { \ cstring tmp = cstr_clone("" cstr, (sizeof(cstr) / sizeof(char)) - 1); \ if (tmp->type == 0) { \ tmp->type = CSTR_PERMANENT; \ tmp->ref = 0; \ } \ if (!__sync_bool_compare_and_swap(&var, NULL, tmp)) { \ if (tmp->type == CSTR_PERMANENT) \ free(tmp); \ } \ } #define CSTR_CLOSE(var) \ do { \ if (!(var)->str->type) \ cstr_release((var)->str); \ } while (0) /* Public API */ cstring cstr_grab(cstring s); cstring cstr_clone(const char *cstr, size_t sz); cstring cstr_cat(cstr_buffer sb, const char *str); int cstr_equal(cstring a, cstring b); void cstr_release(cstring s); ``` - cstr.c ```cpp= #include <errno.h> #include <stdarg.h> #include <stdio.h> #include <string.h> #include "cstr.h" #define INTERNING_POOL_SIZE 1024 #define HASH_START_SIZE 16 /* must be power of 2 */ struct __cstr_node { char buffer[CSTR_INTERNING_SIZE]; struct __cstr_data str; struct __cstr_node *next; }; struct __cstr_pool { struct __cstr_node node[INTERNING_POOL_SIZE]; }; struct __cstr_interning { int lock; int index; unsigned size; unsigned total; struct __cstr_node **hash; struct __cstr_pool *pool; }; static struct __cstr_interning __cstr_ctx; /* FIXME: use C11 atomics */ #define CSTR_LOCK() \ ({ \ while (__sync_lock_test_and_set(&(__cstr_ctx.lock), 1)) { \ } \ }) #define CSTR_UNLOCK() ({ __sync_lock_release(&(__cstr_ctx.lock)); }) static void *xalloc(size_t n) { void *m = malloc(n); if (!m) exit(-1); return m; } static inline void insert_node(struct __cstr_node **hash, int sz, struct __cstr_node *node) { uint32_t h = node->str.hash_size; int index = h & (sz - 1); node->next = hash[index]; hash[index] = node; } static void expand(struct __cstr_interning *si) { unsigned new_size = si->size * 2; if (new_size < HASH_START_SIZE) new_size = HASH_START_SIZE; struct __cstr_node **new_hash = xalloc(sizeof(struct __cstr_node *) * new_size); memset(new_hash, 0, sizeof(struct __cstr_node *) * new_size); for (unsigned i = 0; i < si->size; ++i) { struct __cstr_node *node = si->hash[i]; while (node) { struct __cstr_node *tmp = node->next; insert_node(new_hash, new_size, node); node = tmp; } } free(si->hash); si->hash = new_hash; si->size = new_size; } static cstring interning(struct __cstr_interning *si, const char *cstr, size_t sz, uint32_t hash) { if (!si->hash) return NULL; int index = (int) (hash & (si->size - 1)); struct __cstr_node *n = si->hash[index]; while (n) { if (n->str.hash_size == hash) { if (!strcmp(n->str.cstr, cstr)) return &n->str; } n = n->next; } // 80% (4/5) threshold if (si->total * 5 >= si->size * 4) return NULL; if (!si->pool) { si->pool = xalloc(sizeof(struct __cstr_pool)); si->index = 0; } n = &si->pool->node[si->index++]; memcpy(n->buffer, cstr, sz); n->buffer[sz] = 0; cstring cs = &n->str; cs->cstr = n->buffer; cs->hash_size = hash; cs->type = CSTR_INTERNING; cs->ref = 0; n->next = si->hash[index]; si->hash[index] = n; return cs; } static cstring cstr_interning(const char *cstr, size_t sz, uint32_t hash) { cstring ret; CSTR_LOCK(); ret = interning(&__cstr_ctx, cstr, sz, hash); if (!ret) { expand(&__cstr_ctx); ret = interning(&__cstr_ctx, cstr, sz, hash); } ++__cstr_ctx.total; CSTR_UNLOCK(); return ret; } static inline uint32_t hash_blob(const char *buffer, size_t len) { const uint8_t *ptr = (const uint8_t *) buffer; size_t h = len; size_t step = (len >> 5) + 1; for (size_t i = len; i >= step; i -= step) h = h ^ ((h << 5) + (h >> 2) + ptr[i - 1]); return h == 0 ? 1 : h; } cstring cstr_clone(const char *cstr, size_t sz) { if (sz < CSTR_INTERNING_SIZE) return cstr_interning(cstr, sz, hash_blob(cstr, sz)); cstring p = xalloc(sizeof(struct __cstr_data) + sz + 1); if (!p) return NULL; void *ptr = (void *) (p + 1); p->cstr = ptr; p->type = 0; p->ref = 1; memcpy(ptr, cstr, sz); ((char *) ptr)[sz] = 0; p->hash_size = 0; return p; } cstring cstr_grab(cstring s) { if (s->type & (CSTR_PERMANENT | CSTR_INTERNING)) return s; if (s->type == CSTR_ONSTACK) return cstr_clone(s->cstr, s->hash_size); if (s->ref == 0) s->type = CSTR_PERMANENT; else __sync_add_and_fetch(&s->ref, 1); return s; } void cstr_release(cstring s) { if (s->type || !s->ref) return; if (__sync_sub_and_fetch(&s->ref, 1) == 0) free(s); } static size_t cstr_hash(cstring s) { if (s->type == CSTR_ONSTACK) return hash_blob(s->cstr, s->hash_size); if (s->hash_size == 0) s->hash_size = hash_blob(s->cstr, strlen(s->cstr)); return s->hash_size; } int cstr_equal(cstring a, cstring b) { if (a == b) return 1; if ((a->type == CSTR_INTERNING) && (b->type == CSTR_INTERNING)) return 0; if ((a->type == CSTR_ONSTACK) && (b->type == CSTR_ONSTACK)) { if (a->hash_size != b->hash_size) return 0; return memcmp(a->cstr, b->cstr, a->hash_size) == 0; } uint32_t hasha = cstr_hash(a); uint32_t hashb = cstr_hash(b); if (hasha != hashb) return 0; return !strcmp(a->cstr, b->cstr); } static cstring cstr_cat2(const char *a, const char *b) { size_t sa = strlen(a), sb = strlen(b); if (sa + sb < CSTR_INTERNING_SIZE) { char tmp[CSTR_INTERNING_SIZE]; memcpy(tmp, a, sa); memcpy(tmp + sa, b, sb); tmp[sa + sb] = 0; return cstr_interning(tmp, sa + sb, hash_blob(tmp, sa + sb)); } cstring p = xalloc(sizeof(struct __cstr_data) + sa + sb + 1); if (!p) return NULL; char *ptr = (char *) (p + 1); p->cstr = ptr; p->type = 0; p->ref = 1; memcpy(ptr, a, sa); memcpy(ptr + sa, b, sb); ptr[sa + sb] = 0; p->hash_size = 0; return p; } cstring cstr_cat(cstr_buffer sb, const char *str) { cstring s = sb->str; if (s->type == CSTR_ONSTACK) { int i = s->hash_size; while (i < CSTR_STACK_SIZE - 1) { s->cstr[i] = *str; if (*str == 0) return s; ++s->hash_size; ++str; ++i; } s->cstr[i] = 0; } cstring tmp = s; sb->str = cstr_cat2(tmp->cstr, str); cstr_release(tmp); return sb->str; } ``` ::: TODO <!-- ### 程式原理 如果兩個字串比對後的內容相同,那麼可以將其保存在相同的記憶體空間。 倘若字串長度過長,則比對過程會耗費大量成本,於是程式引入了 hash,藉此降低比對的成本。只要兩者的 hash 相同,即為相同的字串。 #### struct ```cpp typedef struct __cstr_data { char *cstr; uint32_t hash_size; uint16_t type; uint16_t ref; } * cstring; typedef struct __cstr_buffer { cstring str; } cstr_buffer[1]; ``` -->