contributed by < chi-ming5566
>
測驗題
測驗一
此函式是用來判斷指定的記憶體範圍內是否全是有效的 ASCII 字元。
但逐一字元比對的效率低,在 64 位元的架構下,所以嘗試一次比對 64 位元的資料 (即 word size),程式碼改寫如下:
延伸問題:
1.解釋上述程式的運作原理,特別是為何用到 memcpy 呢?
2.將上述技巧應用於「給予一個已知長度的字串,檢測裡頭是否包含有效的英文大小寫字母」
承 (2),考慮常見的英文標點符號,判斷輸入字串是否為有效的字元集,避免逐一字元比對。
提示: Intel SSE 4.2 加入 STTNI (String and Text New Instructions) 指令,可加速字串比對。
payload
會去抓 str
字串陣列中前 8 位與 0x80
進行 & 運算後需等於 0,而 payload
內有 8 個 bytes 對應8個字元,因此每個字元對應一組0x80
,故 MMM = 0x8080808080808080。str + i
指向地址為起始地址的連續 8 個位元組的資料,再複製到以 payload
指向地址為起始地址的空間內。測驗二
開發解析器 (parser) 時,常會將十六進位表示的字串 (hexadecimal string) 轉為數值,例如 '0xF' (大寫 F 字元) 和 '0xf' (小寫 f 字元) 都該轉換為 15。考慮以下不需要分支 (branchless) 的實作:
已知 in
一定隸屬於 '0'
, '1'
, '2'
, …, '9'
及 'a'
, 'b'
, 'c'
, …, 'f'
(小寫) 及 'A'
, 'B'
, 'C'
, …, 'F'
(大寫) 這些字元。預期 hexchar2val('F')
和 hexchar2val('f')
回傳 15,且 hexchar2val('0')
回傳 0,其他輸入都有對應的數值。
以下摘自 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
延伸問題:
1.解釋上述程式的運作原理;
2.將 hexchar2val 擴充為允許 "0xFF", "0xCAFEBABE", "0x8BADF00D" 等字串輸入,且輸出對應的十進位數值
hexchar2val('F')
和 hexchar2val('f')
回傳 15。F
對應0x46
, f
對應 0x66
。0x46
轉換成二進位就是 0100 0110
,而 0x66
則是 0110 0110
。因為return (in + shift) & 0xf
,所以 F
與 f
輸入的 in + shift
最低位的 4 個 bits 必為 1111
,轉換成十進位就是 15 。
Character | Binary |
---|---|
F | 0100 0110 |
f | 0110 0110 |
0x40 | 0100 0000 |
由此可知兩者在最高位 4 個 bits 只有 1 個 bit 相同,於是MASK = ? 0x40
。
Character | Binary |
---|---|
0x40 | 0100 0000 |
letter | 0100 0000 |
F | 0100 0110 |
f | 0110 0110 |
in+shift | XXXX 1111 |
根據 MASK = 0x40 的結果我們可以知道 letter 在兩種輸入情況下皆等於 0100 0000 ,又in + shift 最低位的 4 個 bits 等於 1111 ,於是讓 letter 分別做兩次右移補滿 F 和 f 的最低位 0110 , 於是 |
|
AAA = 3 |
|
BBB = 6 |
測驗三
其中 __uint128_t 是 GCC extension,用以表示 128 位元的無號數值。
預期 quickmod(5) 會得到 2, quickmod(55) 得到 1, quickmod(555) 得到 0 (555 是 3 的倍數)。
請補完程式碼。
XXX = (f) 0xFFFFFFFFFFFFFFFF
測驗四
以 D = 3
來說,divisible(7)
要得到 0
(即 7
無法被 3
整除),divisible(87)
要得到 1
(即白痴是三的倍數)
請補完程式碼。
令 n=3a, 3a+1, 3a+2
(a為非負整數)
若 a=1
, 因為 3M > 0xFFFFFFFFFFFFFFFF
(overflow) 所以 3M = 0x0000000000000002
求三者最小值:
(c) M - 1
符合要求,所以YYY = (c) M - 1
。測驗五
PACKED_BYTE
的作用是將輸入 LSB 的 8 bits 擷取後重複擴充至 64 個 bits,例如: PACKED_BYTE(0xAB)
會得到 0xABABABABABABABAB,如同第一題以0x80
對 ASCII 範圍內的字元進行檢測,因此 VV1 = (e) 0x80
再來看到uint64_t mask = ((A ^ Z) & PACKED_BYTE(VV4)) >> 2
,而
此外0x80
>> 2 = ' '
可以知道 A ^ Z
的數值可以決定這 8 個字元哪一個要做大小寫轉換
因此只有字元為大寫時,A ^ Z
的 MSB 才能為 1
(128 - 'A' + char + VV2) ^ (128 - 'Z' + char + VV3)
char | Decimal | Binary |
---|---|---|
A | 65 | 0100 0001 |
Z | 90 | 0101 1010 |
由此可得知當
char >= 'A'
時 uint64_t A
的 MSB 為 1
char >= 'Z'
時 uint64_t Z
的 MSB 為 1
但當 char = Z
時為了使 A ^ Z
的 MSB 為 1 還需額外減 1
所以
VV2 = (b) 0
VV3 = (a) (-1)
VV4 = (e) 0x80
測驗六
首先建一個真值表:
higher | lower | input | higher_output | lower_output |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 0 | 1 |
1 | 0 | 0 | 1 | 0 |
0 | 0 | 1 | 0 | 1 |
0 | 1 | 1 | 1 | 0 |
1 | 0 | 1 | 0 | 0 |
但題解中 higer
計算並未使用到前次 lower
的狀態,因此修改真值表,
而這次就嘗試使用 lower_output
計算 higher_output
。
higher | lower(lower_output) | input | higher_output |
---|---|---|---|
0 | 0 | 0 | 0 |
0 | 1 | 0 | 0 |
1 | 0 | 0 | 1 |
0 | 1 | 1 | 0 |
0 | 0 | 1 | 1 |
1 | 0 | 1 | 0 |
因此改用lower
計算higher
,得到
KKK = ~higher
JJJ = ~lower