# 2021q1 Homework3 (quiz3) contributed by < `tigger12613` > > [2021q1 第 3 週測驗題](https://hackmd.io/@sysprog/linux2021-quiz3) ## 解釋程式碼運作原理 ### 儲存結構 `xs` 是儲存字串的結構,長度小於等於15的短字串用陣列儲存在 steak ,大於的長字串儲存在 heap。不論長字串、中字串或短字串結構的大小都為 16 byte 。 `filler` 儲存字串內容,如果字串長度小於15則正常儲存,如果字串長度等於15則把結束字符放在第16個 byte 中,因為這時的 `space_left`, `is_large_string`, `is_ptr`, `flag2`, `flag3`全都會是零,剛好與結束字符相同。 `space_left` 字串剩下的空間。 `is_ptr` 是否是以指針的方式儲存。 `is_large_string` 是否是長字串。 `char *ptr` 字串指標。 `size_t size` 字串長度。 `capacity` 使用的記憶體大小,使用`2^capacity`大小的記憶體。 ```cpp= typedef union { /* allow strings up to 15 bytes to stay on the stack * use the last byte as a null terminator and to store flags * much like fbstring: * https://github.com/facebook/folly/blob/master/folly/docs/FBString.md */ char data[16]; struct { uint8_t filler[15], /* how many free bytes in this stack allocated string * same idea as fbstring */ space_left : 4, /* if it is on heap, set to 1 */ is_ptr : 1, is_large_string : 1, flag2 : 1, flag3 : 1; }; /* heap allocated */ struct { char *ptr; /* supports strings up to 2^MAX_STR_LEN_BITS - 1 bytes */ size_t size : MAX_STR_LEN_BITS, /* capacity is always a power of 2 (unsigned)-1 */ capacity : 6; /* the last 4 bits are important flags */ }; } xs; ``` ### Functions ### xs_is_ptr 字串是否儲存在 heap。 ```cpp= static inline bool xs_is_ptr(const xs *x) { return x->is_ptr; } ``` ### xs_is_large_string 字串是否大於等於`LARGE_STRING_LEN`。(`LARGE_STRING_LEN = 256`) ```cpp= static inline bool xs_is_large_string(const xs *x) { return x->is_large_string; } ``` ### xs_size 字串長度。 ```cpp= static inline size_t xs_size(const xs *x) { return xs_is_ptr(x) ? x->size : 15 - x->space_left; } ``` ### xs_data 回傳資料位置。 如果是中字串或長字串回傳指標。如果是長字串,回傳字串的 `4 byte` 後的位置,那 `4 byte` 是用來儲存引用數量的,暫時不會用到。 `OFF` = 1 因為 large string 的前 `4 byte` 用來儲存引用數量,後面才是字串內容。 ```cpp= static inline char *xs_data(const xs *x) { if (!xs_is_ptr(x)) return (char *) x->data; if (xs_is_large_string(x)) return (char *) (x->ptr + OFF); return (char *) x->ptr; } ``` ### xs_capacity 獲得 `capacity` 的大小。 ```cpp= static inline size_t xs_capacity(const xs *x) { return xs_is_ptr(x) ? ((size_t) 1 << x->capacity) - 1 : 15; } ``` ### xs_set_refcnt 設定引用數量,只對長字串有意義。 ```cpp= static inline void xs_set_refcnt(const xs *x, int val) { *((int *) ((size_t) x->ptr)) = val; } ``` ### xs_inc_refcnt 引用數量加一,只對長字串有意義。 ```cpp= static inline void xs_inc_refcnt(const xs *x) { if (xs_is_large_string(x)) ++(*(int *) ((size_t) x->ptr)); } ``` ### xs_dec_refcnt 引用數量減一,只對長字串有意義。 ```cpp= static inline int xs_dec_refcnt(const xs *x) { if (!xs_is_large_string(x)) return 0; return --(*(int *) ((size_t) x->ptr)); } ``` ### xs_get_refcnt 獲取引用數量。 ```cpp= static inline int xs_get_refcnt(const xs *x) { if (!xs_is_large_string(x)) return 0; return *(int *) ((size_t) x->ptr); } ``` ### ilog2 以二為底對 `uint32` 取 `log` 並且 round down 。 ```cpp= static inline int ilog2(uint32_t n) { return 32 - __builtin_clz(n) - 1; } ``` ### xs_allocate_data 分配或重新分配記憶體,記憶體大小一定是二的次方或二的次方加四。 ```cpp= static void xs_allocate_data(xs *x, size_t len, bool reallocate) { /* Medium string */ if (len < LARGE_STRING_LEN) { x->ptr = reallocate ? realloc(x->ptr, (size_t) 1 << x->capacity) : malloc((size_t) 1 << x->capacity); return; } /* Large string */ x->is_large_string = 1; /* The extra 4 bytes are used to store the reference count */ x->ptr = reallocate ? realloc(x->ptr, (size_t)(1 << x->capacity) + 4) : malloc((size_t)(1 << x->capacity) + 4); xs_set_refcnt(x, 1); } ``` ### xs_new 新增 `xs` ,短字串放在原位,中長字串額外新增一個記憶體存放。 ```cpp= xs *xs_new(xs *x, const void *p) { *x = xs_literal_empty(); size_t len = strlen(p) + 1; if (len > 16) { x->capacity = ilog2(len) + 1; x->size = len - 1; x->is_ptr = true; xs_allocate_data(x, x->size, 0); memcpy(xs_data(x), p, len); } else { memcpy(x->data, p, len); x->space_left = 15 - (len - 1); } return x; } ``` ### xs_grow 加大分配的記憶體。第17行的 else 永遠不會發生,第 12 行保證 is_ptr = true。 如果把實際上 `is_ptr == false` 的字串重新分配記憶體會發生記憶體錯誤,因此刪去`x->is_ptr = true`,加在真的分配記憶體之後。 因為 `xs_capacity` 對 `is_ptr == false` 的字串會回傳 `15` 因此對增大後的長度小於 16 的字串將不額外分配記憶體。 ```cpp= xs *xs_grow(xs *x, size_t len) { char buf[16]; if (len <= xs_capacity(x)) return x; /* Backup first */ if (!xs_is_ptr(x)) memcpy(buf, x->data, 16); //x->is_ptr = true; x->capacity = ilog2(len) + 1; if (xs_is_ptr(x)) { xs_allocate_data(x, len, 1); } else { xs_allocate_data(x, len, 0); memcpy(xs_data(x), buf, 16); x->is_ptr = true; } return x; } ``` ### xs_newempty 新增一個空字串。 ```cpp= static inline xs *xs_newempty(xs *x) { *x = xs_literal_empty(); return x; } ``` ### xs_free 釋放使用的記憶體,回傳一個空字串。 ```cpp= static inline xs *xs_free(xs *x) { if (xs_is_ptr(x) && xs_dec_refcnt(x) <= 0) free(x->ptr); return xs_newempty(x); } ``` ### xs_cow_lazy_copy 如果不是長字串 xs_get_refcnt 為 0 ,回傳 false 。 如果是長字串而且被引用超過2個就必須分配一個新的記憶體並且把字串複製過去。 當字串面臨更改就必須使用此函式,去確認是否要分配一個新的記憶體。 因為現在沒有實作 `CoW` 因此現在只會回傳 false 。 ```cpp= static bool xs_cow_lazy_copy(xs *x, char **data) { if (xs_get_refcnt(x) <= 1) return false; /* Lazy copy */ xs_dec_refcnt(x); xs_allocate_data(x, x->size, 0); if (data) { memcpy(xs_data(x), *data, x->size); /* Update the newly allocated pointer */ *data = xs_data(x); } return true; } ``` ### xs_concat 給 `string` 字串加上頭尾字串。 因為 `string` 面臨更改所以呼叫 `xs_cow_lazy_copy`。 如果連接完的字串大小小於等於容量,就不用增大記憶體,反之則要增大記憶體。 ```cpp= xs *xs_concat(xs *string, const xs *prefix, const xs *suffix) { size_t pres = xs_size(prefix), sufs = xs_size(suffix), size = xs_size(string), capacity = xs_capacity(string); char *pre = xs_data(prefix), *suf = xs_data(suffix), *data = xs_data(string); xs_cow_lazy_copy(string, &data); if (size + pres + sufs <= capacity) { memmove(data + pres, data, size); memcpy(data, pre, pres); memcpy(data + pres + size, suf, sufs + 1); if (xs_is_ptr(string)) string->size = size + pres + sufs; else string->space_left = 15 - (size + pres + sufs); } else { xs tmps = xs_literal_empty(); xs_grow(&tmps, size + pres + sufs); char *tmpdata = xs_data(&tmps); memcpy(tmpdata + pres, data, size); memcpy(tmpdata, pre, pres); memcpy(tmpdata + pres + size, suf, sufs + 1); xs_free(string); *string = tmps; string->size = size + pres + sufs; } return string; } ``` ### xs_trim 清除字串前後指定的字元。 ```cpp= xs *xs_trim(xs *x, const char *trimset) { if (!trimset[0]) return x; char *dataptr = xs_data(x), *orig = dataptr; if (xs_cow_lazy_copy(x, &dataptr)) orig = dataptr; /* similar to strspn/strpbrk but it operates on binary data */ uint8_t mask[32] = {0}; #define check_bit(byte) (mask[(uint8_t) byte / 8] & 1 << (uint8_t) byte % 8) #define set_bit(byte) (mask[(uint8_t) byte / 8] |= 1 << (uint8_t) byte % 8) size_t i, slen = xs_size(x), trimlen = strlen(trimset); for (i = 0; i < trimlen; i++) set_bit(trimset[i]); for (i = 0; i < slen; i++) if (!check_bit(dataptr[i])) break; for (; slen > 0; slen--) if (!check_bit(dataptr[slen - 1])) break; dataptr += i; slen -= i; /* reserved space as a buffer on the heap. * Do not reallocate immediately. Instead, reuse it as possible. * Do not shrink to in place if < 16 bytes. */ memmove(orig, dataptr, slen); /* do not dirty memory unless it is needed */ if (orig[slen]) orig[slen] = 0; if (xs_is_ptr(x)) x->size = slen; else x->space_left = 15 - slen; return x; #undef check_bit #undef set_bit } ``` ### set_bit `uint8_t mask[32] = {0}`總共有 256 個 bit 因此把每個 `byte` 一一對應到這 256 個 bit 上,如果某個 `byte` 需要被標記就把 屬於它的 bit 設為 1 。 ```cpp= #define set_bit(byte) (mask[(uint8_t) byte / 8] |= 1 << (uint8_t) byte % 8) ``` ### check_bit 確認 `byte` 是不是被標記的 `byte` ,原理與 `set_bit` 相同。 ```cpp= #define check_bit(byte) (mask[(uint8_t) byte / 8] & 1 << (uint8_t) byte % 8) ```