# 2019q3 Homework1 (review) contribured by `<` [Ting199708](https://github.com/Ting199708) `>` --- ## 第一周測驗1 考慮以下 C 程式的 align4 巨集的作用是,針對 4-byte alignment,找到給定地址的 round up alignment address。 ### 程式目的 * 對齊資料的記憶體位址 ### 題目程式碼 ``` c #include <stdio.h> #define align4(x) (((x) + K) & (-4)) int main(void) { int p = 0x1997; printf("align4(p) is %08x\n", align4(p)); return 0; } ``` ### Why we need Data Alignment * 早期電腦記憶體只能以word為單位進行存取 * 電腦的記憶體通常設計成以word-sized來存取最有效率,而現代的電腦系統word通常為4 bytes or 8 bytes ### 分析與想法 了解題目後我先將p=0x1997代入觀察 ```c align(p) = (0x1997 + K) & (-4) = 0x1998 ``` 且依題意,p=0x1995, 0x1996, 0x1997, 0x1998,align(p)均等於0x1998,將其轉換為二進位觀察,分析如下: ```c 0x1995: (0001 1001 1001 0101 + K) & 1111 1111 1111 1100 = 0001 1001 1001 1000 0x1996: (0001 1001 1001 0110 + K) & 1111 1111 1111 1100 = 0001 1001 1001 1000 0x1997: (0001 1001 1001 0111 + K) & 1111 1111 1111 1100 = 0001 1001 1001 1000 0x1998: (0001 1001 1001 1000 + K) & 1111 1111 1111 1100 = 0001 1001 1001 1000 ``` 經過二進位觀察後,發現最後4 bit需在加上數值K後變成10xx 所以,K=3,答案為(g) ### 延伸題目 Survey Linux Kernel Document後,我找到了以下function有用到aligment的概念 ```c void blk_quene_aligment_offset(struct request_quene *q, unsigned int offset) Parameters: stuct request_quene *q: the request qune for the device unsigned int offset: alignment offset in bytes ``` 核心文件對此function解釋如下: > Some devices are naturally misaligned to compensate for things like the legacy DOS partition table 63-sector offset. Low-level drivers should call this function for devices whose first sector is not naturally aligned. 程式原始碼(/block/blk-setting.c, line 375): ```c void blk_quene_alignment_offset(struct request_quene *q, unsigned int offset) { q->limits.alignment_offset = offset & (q->limits.physiccal_block_size - 1); q->limits.misaligned = 0; } ``` 應用範圍(/drivers/scsi/sd.c, line 2333): ```c alignment = ((buffer[14] & 0x3f) << 8 | buffer[15]) * sector_size blk_quene_alignment_offset(sdp->request_quene, alignment); ``` 由此可見,blk_quene_alignment_offset可根據使用的情況來對request_quene的offset進行alignment。 ### Reference * 關於記憶體對齊: http://opass.logdown.com/posts/743054-about-memory-alignment * The Linux Kernel: https://www.kernel.org/doc/html/latest/core-api/kernel-api.html?highlight=alignment#c.blk_queue_alignment_offset * Linux Source Code: https://elixir.bootlin.com/linux/latest/source --- ## 第一周測驗3 考慮以下C程式碼: ```c #include <stdbool.h> bool func(unsigned int x) { return x && ((x & (~x + 1)) == x); } ``` 可改寫為以下等價程式碼: ```c bool func(unsigned int x) { unsigned int unknown = 0; while (x && unknow <= 1) { if ((x & Z) == 1) unknown++; x >>= 1; } return (unknown == 1); } ``` ### 分析與想法 首先我看到了原始C程式碼我對於將unsigned int x做&&運算有點疑問,因此我實際實驗了一次。 再來我將X=0~8代入程式進行分析,結果如下 ```c x = 0000 -> ((x & (~x + 1))) = 0000 x = 0001 -> ((x & (~x + 1))) = 0001 x = 0010 -> ((x & (~x + 1))) = 0010 x = 0011 -> ((x & (~x + 1))) = 0001 x = 0100 -> ((x & (~x + 1))) = 0100 x = 0101 -> ((x & (~x + 1))) = 0001 x = 0110 -> ((x & (~x + 1))) = 0010 x = 0111 -> ((x & (~x + 1))) = 0001 x = 1000 -> ((x & (~x + 1))) = 1000 ``` 由此可知,當x=0時func輸出為false,x!=0時,因為x在&&運算時視為True,所以func(x)結果為: x==(x & (~x + 1)),即x=2的次冪時,結果為True 再來,分析下方等價程式碼,x=0時,其輸出為False,x!=0時,則需檢測x內所含的1的數量 因此,透過Z=0001和x做&運算,再將x向右移1 bit,即可計算x內1的bit數,若僅有1個"1",則unknow=1,回傳True;反之,若"1"的數量大於1個,則unknow=2,跳出迴圈,回傳False。 故,此題解答為(e) 1 ### 延伸題目 透過bitwise運算計算兩數字中的最大或最小值 #### 程式碼 ```cpp int find_min(int x, int y) { return y + ((x - y) & ((x - y) >> (sizeof(int) * CHAR_BIT - 1))); } int find_max(int x, int y) { return x - ((x - y) & ((x - y) >> (sizeof(int) * CHAR_BIT - 1))); } ``` #### 分析 假設(x,y)=(14,24) and (24,14) * 當(x,y)=(14,24) ``` (x-y) >> (sizeof(int) * CHAR_BIT -1) = -1 所以(x - y) & ((x - y) >> (sizeof(int) * CHAR_BIT - 1)) = (x - y) = -10 find_min(x, y) = y + (-10) = 14 find_max(x, y) = x - (-10) = 24 ``` * 當(x,y)=(24,14) ``` (x-y) >> (sizeof(int) * CHAR_BIT -1) = 0 所以(x - y) & ((x - y) >> (sizeof(int) * CHAR_BIT - 1)) = 0 find_min(x, y) = y + 0 = 14 find_max(x, y) = x - 0 = 24 ``` 可見此程式的關鍵為透過(x-y)再右移31 bits,取得sing bit來達成判斷其大小的目的 ### Reference * Bit Twiddling Hacks: https://graphics.stanford.edu/~seander/bithacks.html --- ## 課程說明ppt第15頁 分析程式,並考慮abs(-2147483648)所得到的結果 ### 題目程式碼 ```cpp #include <stdint.h> int32_t abs(int32_t x) { int32_t mask = (x >> 31); return (x + mask) ^ mask; } ``` ### 電腦的2補數表示法 二補數是一種用二進位表示有號數的方法,以下用一簡單範例來說明 ``` 24 -> 0000 0000 0000 0000 0000 0000 0001 1000 -24 -> 1111 1111 1111 1111 1111 1111 1110 1000 ``` 由上方範例可發現,只要將24的每個bit反向再加一,即可得到其負數(-24) 的二進位表示 ### 分析與想法 首先先來分析此程式如何運作,由於電腦在儲存負數時是以二補數方式表示,因此,abs程式只要將負數再做一次二補數運算即可得到abs運算後的數值。 * X為正數 ``` 首先分析X是正數的情況,因為正數的sign-bit為0,因此mask = (x >> 31)會等於0。 而因為mask=0,所以return value會等於x,也驗證了正數做abs運算後數值不變 ``` * X是負數 ``` 再來,若X為負數,因為負數的sign-bit為1,因此mask = (x >> 31)會等於 -1(1111 1111 1111 1111 1111 1111 1111 1111) 將此mask與X相加可以發現變為abs(X)的反向,又任何數和1做xor運算等效為not, 因此(x + mask) ^ mask即會輸出X的abs運算結果。 ``` ### 關於abs(-2147483648)輸出結果 經過實際測試,abs(-2147483648)的輸出結果為-2147483648,很明顯的,此程式對於輸入的數字有所限制。 實際執行程式碼發現輸出數值錯誤: :::success 實驗結果: Please input the x: -2147483648 abs(-2147483648) = -2147483648 ::: :::danger 文字訊息==不要==用圖片來展現 :notes: jserv ::: 分析程式原始碼,發現int32_t在儲存有號整數時,其範圍為-2147483647~2147483647,因此-2147483648儲存進去時會發生overflow的現象導致數值不正確 當 x=-2147483647及 x=2147483647時,程式均可正常運作,可見上述分析正確: :::success 實驗結果: Please input the x: -2147483647 abs(-2147483647) = 2147483647 ::: :::success 實驗結果: Please input the x: 2147483647 abs(-2147483647) = 2147483647 ::: :::danger 文字訊息==不要==用圖片來展現 :notes: jserv ::: ### 解決方法 因此,若要解決此問題,可將程式修改如下: ```c #include <stdint.h> int64_t abs(int64_t x) { int64_t mask = (x >> 63); return (x + mask) ^ mask; } ``` 執行結果: :::success 實驗結果: Please input the x: -2147483648 abs(-2147483648) = 2147483648 ::: 當然,將此程式修改後X仍有限制,其範圍為 $-2^{63}$ ~ $2^{63}$,不過對大多數情況皆已可適用。 ### Reference * [Wikipedia: Two's complement](https://en.wikipedia.org/wiki/Two%27s_complement) :::danger 避免引用中文 Wikipedia 詞目,因為後者可能會跟英文詞目內容不同步 :notes: jserv :::