--- tags: linux2022 --- # 2022-04-04 quiz8 contributed by < `blueskyson` > [題目連結](https://hackmd.io/@sysprog/linux2022-quiz8) ## 測驗 1 首先看 `memchr_opt` 的前 12 行: ```c= void *memchr_opt(const void *src_void, int c, size_t length) { const unsigned char *src = (const unsigned char *) src_void; unsigned char d = c; while (UNALIGNED(src)) { if (!length--) return NULL; if (*src == d) return (void *) src; src++; } ``` 其中 `UNALIGNED` 巨集定義如下: ```c #define UNALIGNED(X) ((long) X & (sizeof(long) - 1)) ``` 當 `UNALIGNED` 的 `X` 是對齊 `long` 的位址時,`UNALIGNED(X)` 的值為 `0`,因此在第 6 到 12 行的迴圈中,如果 `*src` 為字元 `d`,就直接回傳其所在的位址,否則 while 迴圈會於 `src` 指向對齊 `long` 的位址時跳出。 在 64 位元的系統上對齊 `long` 的位址為 8 的倍數,例如 `0x01000`、`0x10000` 等。 ```c=13 if (!TOO_SMALL(length)) { /* The fast code reads the source one word at a time and only performs * the bytewise search on word-sized segments if they contain the search * character, which is detected by XORing the word-sized segment with a * word-sized block of the search character and then detecting for the * presence of NULL in the result. */ unsigned long *asrc = (unsigned long *) src; unsigned long mask = d << 8 | d; mask = mask << 16 | mask; for (unsigned int i = 32; i < LBLOCKSIZE * 8; i <<= 1) mask = (mask << i) | mask; while (length >= LBLOCKSIZE) { /* XXXXX: Your implementation should appear here */ } /* If there are fewer than LBLOCKSIZE characters left, then we resort to * the bytewise loop. */ src = (unsigned char *) asrc; } ``` 其中 `LBLOCKSIZE` 與 `TOO_SMALL` 巨集定義如下: ```c #define LBLOCKSIZE (sizeof(long)) #define TOO_SMALL(LEN) ((LEN) < LBLOCKSIZE) ``` 當剩餘字串長度大於等於 `long` 的大小(4 byte 或 8 byte)時就進入第 13 行 if 條件。 第 21 到 24 行的作用是將 1 byte 的 `d` 擴展成 4 byte 或 8 byte 的 `mask`。舉例來說,如果 `d` 的值為 `0x2e`,則 `mask` 為 `0x2e2e2e2e2e2e2e2e`。 接下來就是實作第 26 行 while 迴圈,這裡老師提供了 `DETECT_CHAR` 巨集來偵測哪個位置的值為 `d` 的值: ```c #define DETECT_NULL(X) (((X) -0x0101010101010101) & ~(X) & 0x8080808080808080) #define DETECT_CHAR(X, MASK) (DETECT_NULL(X ^ MASK)) ``` 為了釐清 `DETECT_CHAR` 的作用,我將每一步運算印出,舉 `"//wiki.c"` 為例,從中偵測字元 `'.'`: ```c int main() { const char *src = "http://wiki.csie.ncku.edu.tw"; char d = '.'; while (UNALIGNED(src)) src++; unsigned long mask = d << 8 | d; mask = mask << 16 | mask; for (unsigned int i = 32; i < LBLOCKSIZE * 8; i <<= 1) mask = (mask << i) | mask; unsigned long *asrc = (unsigned long *) src; unsigned long xor = *asrc ^ mask; printf("\"%.8s\"\n", src); printf("%016lx\n", *asrc); printf("%016lx\n", xor); printf("%016lx\n", xor - 0x0101010101010101); printf("%016lx\n", (xor - 0x0101010101010101) & ~xor); printf("%016lx\n", (xor - 0x0101010101010101) & ~xor & 0x8080808080808080); return 0; } ``` 輸出如下: ```bash "://wiki." # src 2e696b69772f2f3a # asrc 0047454759010114 # xor = asrc ^ mask ff46444658000013 # xor - 0x0101010101010101 ff00000000000003 # (xor - 0x0101010101010101) & ~xor 8000000000000000 # DETECT_CHAR(asrc, mask) ``` 1. 首先可以發現將 `"://wiki."` 以 `unsigned long` 讀取時,位元組的順序會倒過來,即 `2e696b69772f2f3a` 對應到 `".ikiw//:"`。 2. 接下來 `asrc ^ mask` 會得到 `0047454759010114` 可以發現 `00` 即對應到 `'.'` 的位置,我將 `asrc ^ mask` 的結果存到變數 `xor`。 3. `xor - 0x0101010101010101` 會將 `xor` 中的 `00` 轉變為 `fe` 或 `ff`。 4. 在上面的例子中,跳過 `& ~xor` 這一步驟是 ok 的,我推論這個步驟是為了防止超過 ASCII 範圍(大於 127)的字元影響到下一步的判斷。經過這一步驟可以強制將 5. 最後一步將前面運算的結果 `& 0x8080808080808080` 就能將所有 `fe` 與 `ff` 提取出來變成 `80`,並且其他 byte 變為 `00`。 ### 解題 分析完前述程式碼的用途,開始補完上程式碼 ```c while (length >= LBLOCKSIZE) { /* XXXXX: Your implementation should appear here */ unsigned long bits = DETECT_CHAR(*asrc, mask); if (bits) return (char*)asrc + (__builtin_ctzl(bits) >> 3); asrc = asrc + 1; length = length - 8; } ``` 首先用變數 `bit` 儲存 `DETECT_CHAR` 的結果,接下來先判斷 `bits` 是否大於 `0`。若是則代表找到所求字元,透過 `__builtin_ctzl(bits) >> 3` 得到該字元與 `asrc` 的距離,最後回傳 `asrc` 轉型為 `char*` 加上該字元與 `asrc` 的距離。 ### 後續程式碼 ```c=35 while (length--) { if (*src == d) return (void *) src; src++; } return NULL; } ``` 最後若剩餘的字串長度不足 `long` 的長度,則透過第 35 行的 while 迴圈找尋所求字元。若還是找不到就回傳 `NULL`。 ## 測驗 2 ## 測驗 3