# 2020q3 Homework2 (quiz2) contributed by < `Weiting0114` > > [測驗題目](https://hackmd.io/@sysprog/2020-quiz2) ## Test1 * ASCII 是以 7 位元二進位數字進行編碼,可表示 128 個字元,第 128~255 號為擴展字元 (extended ASCII)。 * 二進位表示 0~127 為 `0000 0000`~`0111 1111` ,因此只要第 8 個 bit 為 1 ,該欄位便不為有效的 ASCII 字元。 * 因此,我們可以利用 bitwise 操作,判別第 8 個 bit 是否為 0。 >1個 char 的大小 = 1 byte = 8 bits > >0x80 = (1000 0000)~2~ ### 版本一:逐字比較 ```c= #include <stddef.h> bool is_ascii(const char str[], size_t size) { if (size == 0) return false; for (int i = 0; i < size; i++) if (str[i] & 0x80) /* i.e. (unsigned) str[i] >= 128 */ return false; return true; } ``` * 參數 1. <span class="blue">const char</span> str[]:該字串第一個字元的記憶體地址,而 `const` 為 constant 的縮寫,表示恆常不變的數值。 2. `size_t` 真實型別與作業系統有關,在64位元中為 long long unsigned int,非64位系統中為 long unsigned int。 * 程式 1. `str[i]` 為該字串的第一個字,每個英文字母可以透過 ASCII 轉成一組對應的二進制,故可以用來和0x80做運算。 >範例:char str[] = "Hello" , str[1] = 'H' 2. 為何使用 <span class="red">`0x80`</span> ? <span class="red">`0x80`</span> 為(`80`)~16~ 也相等於 (`1000 0000`)~2~,所以將 `str[i]` 與 (`1000 0000`)~2~ 做 `AND` 運算即可得知 `str[i]` 是否為有效的 ASCII 字元。 > 假設比對 "H" 是否屬於 ASCII,我們知道 "H" 轉為數字對應到 (`0x48`)~16~ = (`0100 1000`)~2~。 再來做 **(`1000 0000`)~2~ AND (`0100 1000`)~2~** > 0x80 : 1000 0000 > 0x41 : 0100 0001 > AND結果:0000 0000 > 所以 return true, "H" 為 ASCII 3. 由於此版本是每個字元逐一比對,效率較低,為了改善這個問題,在第二個版本嘗試一次比對 64 位元的資料。 **在 64 位元的架構下,嘗試一次比對 64 位元的資料 (即 word size)** ```c= #include <stdbool.h> #include <stddef.h> #include <stdint.h> #include <string.h> bool is_ascii(const char str[], size_t size) { if (size == 0) return false; int i = 0; while ((i + 8) <= size) { uint64_t payload; memcpy(&payload, str + i, 8); if (payload & MMM) //MMM = 0x8080808080808080 return false; i += 8; } while (i < size) { if (str[i] & 0x80) return false; i++; } return true; } ``` * **運作原理 :** ```c while ((i + 8) <= size) { uint64_t payload; memcpy(&payload, str + i, 8); if (payload & MMM) //MMM = 0x8080808080808080 return false; i += 8; } ``` * 如果比較的字串大於或等於一個 word (假設 word size = 8 bytes = 64 bits) 則一次將一個 word 大小的資料拷貝進 payload 變數。 * 對於 payload 變數中的每一個 byte ,如果值 >= 128 (0x80) 則不屬於 ascii。 ```c while (i < size) { if (str[i] & 0x80) return false; i++; } ``` * 剩餘不滿一個 word 的資料則一次一個 byte 將剩餘的資料判斷完畢。 ### 延伸問題 ::::info 1.為何特別用到 memcpy 呢? :::: * 查看 memcpy 在 [glibc](https://reurl.cc/Q3mlaZ) 中的原始碼發現不但使用記憶體長度直接比較之外,還有執行對齊的處理,讓執行更有效率。 ## Test2 #### 題目解析: 定義一函式,可將輸入的十六進位表示字元 0~9, a~f or A~F,轉換為十進位數值 0~15。 ```c uint8_t hexchar2val(uint8_t in) { const uint8_t letter = in & MASK; const uint8_t shift = (letter >> AAA) | (letter >> BBB); return (in + shift) & 0xf; } ``` 根據題目中引述: > 以下摘自 ASCII 表格 >* '0', '1', '2', …, '9' 對應到 0x30, 0x31, 0x32, … 0x39 >* 'a', 'b', 'c', …, 'f' (小寫) 對應到 0x61, 0x62, 0x63, …, 0x66 >* 'A', 'B', 'C', …, 'F' 對應到 0x41, 0x42, 0x43, …, 0x46 從下列表格可以發現,數字和字母的差異位於 b~6~,數字 b~6~ 為 0 , 數字 b~6~ 為 0。 | char | 二進制(b~7~b~6~b~5~b~4~ b~3~b~2~b~1~b~0~) | | ---- |:--------------:| | 0 | (0011 0000)~2~ | | 5 | (0011 0101)~2~ | | 9 | (0011 1001)~2~ | | A | (0100 0001)~2~ | | a | (0110 0001)~2~ | | F | (0100 0110)~2~ | | f | (0110 0110)~2~ | ```cpp=3 const uint8_t letter = in & MASK; ``` 因此此處 **MASK** 應選擇 `(c) 0x40`, 將該位元分離出來,判斷目前輸入的字元是英文抑或是數字。 ```cpp=4 const uint8_t shift = (letter >> AAA) | (letter >> BBB); ``` >同樣從上述表格中可發現,若僅看 b~3~b~2~b~1~b~0~ 四個位元, '0' ~ '9' 可直接對應到數值 0 ~ 9, 'a' ~ 'f' 和 'A' ~ 'F' 則是直接對應到數值 1 ~ 6,距離題目要求的數值須再加 9。 因此此處的 `shift` 變數則是使其若為數字時為 0,英文字母則為 9 作為偏移量,故 **AAA 選擇 `(a) 3`**,將字母特有左二的 1 移至右四變為 (0000 1000)~2~ **BBB 選擇 `(b) 6`**,將字母特有左二的 1 移至右一變為 (0000 0001)~2~ 再進行 Bitwise OR 運算變成 (0000 1001)~2~,即為位移量 9 (若為數字則會是 (0000 0000)~2~ )。 ```cpp=5 return (in + shift) & 0xf; ``` 最後將輸入字元加上該位移量,並與 `0xf` 做 Bitwise AND 運算取右四位元,結果即會符合函式需求。 ## Test3 ## Test4 ## Test5 ## Test6 <style> .blue { color: #33A3FF ; } .red{ color: #BE1221; } </style>