# 2018q3 Homework3 (review) contributed by <`AlecJY`> ## 1-2 一開始會先建立一個 $2^8$ 的 lookup table | 十進位 | 二進位 | Parity Check | | ----- | ----- | ------------ | | 0 | 0000 | 0 | | 1 | 0001 | 1 | | 2 | 0010 | 1 | | 3 | 0011 | 0 | | 4 | 0100 | 1 | | 5 | 0101 | 0 | | 6 | 0110 | 0 | | 7 | 0111 | 1 | | 8 | 1000 | 1 | | 9 | 1001 | 0 | | 10 | 1010 | 0 | | 11 | 1011 | 1 | | 12 | 1100 | 0 | | 13 | 1101 | 1 | | 14 | 1110 | 1 | | 15 | 1111 | 0 | 上面這張 lookup table 只包含 4 bit 的部分,可以發現每四個 bit 依循著 n, n^1, n^1, n 的規則,而每 4 個 bit 作為一組的時候每一組也依循著 n, n^1, n^1, n 的規則 由於輸入的是一個 32 bit 的數字, lookup table 只有做 8 bit 數字的部分,因此在迴圈的地方先做了 `num ^= 16;` 和 `num ^= 8;` 將問題化簡為 8 bit。 ## 2-3 在某些情況可以避免因為 operator 優先順序問題而做了錯誤的操作。例如 `head` 傳入一個變數 `*l` ,因為 `->` 的優先順序比 `*` 高,因此在沒有 `()` 的情況下就會先對 l 做 access member through pointer 再把得到的值 dereference ## 3-1 ``` C= #include <stdio.h> #include <stdint.h> struct test { unsigned int x : 5; unsigned int y : 5; unsigned int z; }; int main() { struct test t; printf("Offset of z in struct test is %ld\n", (uintptr_t) &t.z - (uintptr_t) &t); return 0; } ``` 首先先看一下在 GNU/Linux x86_64 環境中執行是怎麼得到 ``` Offset of z in struct test is 4 ``` 變數 x 和 y 是 bit-field ,根據 C99 規格書的說法 > An implementation may allocate any addressable storage unit large enough to hold a bitfield. If enough space remains, a bit-field that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit. If insufficient space remains, whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is implementation-defined. > -- ISO/IEC 9899:1999 p.102 x 佔了 5 bits , y 也佔了 5 bits,兩個相加為 10 bits ,假設一個 unsigned int 的大小為 32 bits ,夠同時放進 x 和 y ,所以 &t.z 到 &t 之間只使用了 4 bytes。 按照這種說法,如果我們把程式碼改一下 ``` C= #include <stdio.h> #include <stdint.h> struct test { unsigned int x : 30; unsigned int y : 5; unsigned int z; }; int main() { struct test t; printf("Offset of z in struct test is %ld\n", (uintptr_t) &t.z - (uintptr_t) &t); return 0; } ``` 這個時候 x 和 y 所佔的 bit 數總和就是 35 ,大於一個 unsigned int 的大小,所以這個時候占用的記憶體空間就變成 ``` Offset of z in struct test is 8 ``` 這題題目在問的是把程式碼改成底下這樣 ``` C= #include <stdio.h> #include <stdint.h> struct test { unsigned int x : 5; unsigned int y : 5; unsigned int z; }; int main() { struct test t; printf("Address of t.x is %p", &t.x); return 0; } ``` 到底可不可以編譯,結果會如何,而答案是 ``` (e) 編譯失敗,不能將指標指向沒有對齊 1 byte 的結構體成員; ``` 會讓人想如果把 x 的 bit 數換成 8 或是 32 能不能編譯成功,結果很顯然的是不行的, GCC 都會產生類似以下的錯誤 ``` test.c: In function ‘main’: test.c:10:36: error: cannot take address of bit-field ‘x’ printf("Address of t.x is %p", &t.x); ``` 這個錯誤訊息已經很明白的說明了不能取 bit-field 的 address ,如果去翻閱 C99 規格書也可以找到以下解釋 > The operand of the unary & operator shall be either a function designator, the result of a [] or unary * operator, or an lvalue that designates an object that is not a bit-field and is not declared with the register storage-class specifier > -- ISO/IEC 9899:1999 p.78 在 102 頁的註釋也有再次提醒 > The unary & (address-of) operator cannot be applied to a bit-field object; thus, there are no pointers to or arrays of bit-field objects. > -- ISO/IEC 9899:1999 p.102 所以並不存在任何指向 bit-field 的指標,也沒辦法取得 bit-field 的 address ## 3-2 這題利用 union 將不同 data type 存在同一個記憶體空間的特性來判斷 byte order ,在 `c.a` 存入 1 ,在 big endian 的環境下記憶體裡會存成 16 進位的 `00 00 00 01` ,在 little endian 的環境下會儲存成 `01 00 00 00` ,這樣讀取第一個 byte 看看是 0 還是 1 就知道是 little endian 還是 big endian ### 找出類似的程式碼 在 Stack Overflow 上面有找到類似的程式碼 [來源](https://stackoverflow.com/a/8979034) ``` C inline int IsBigEndian() { int i=1; return ! *((char *)&i); } ``` 一樣利用了 int 在 big endian 環境下還有 little endian 環境下儲存方式不同的概念來找出 byte order ,只是這邊是利用轉換指標的 type 來取得 int 的第一個 byte ### 其他判斷 Byte Order 的方法 在第一次作業的時候有找到某段程式碼跟轉換 Byte Order 有關係,想說從那份程式碼找找看他怎麼判斷 Byte Order,結果找到下面這段[程式碼](https://github.com/Stichting-MINIX-Research-Foundation/minix/blob/03ac74ede908465cc64c671bbd209e761dc765dc/include/arpa/nameser_compat.h#L52) ``` C #if defined(vax) || defined(ns32000) || defined(sun386) || defined(i386) || \ defined(MIPSEL) || defined(_MIPSEL) || defined(BIT_ZERO_ON_RIGHT) || \ defined(__i386__) || defined(__i386) || defined(__amd64__) || \ defined(__x86_64__) || defined(MIPSEL) || defined(_MIPSEL) || \ defined(BIT_ZERO_ON_RIGHT) || defined(__alpha__) || defined(__alpha) || \ (defined(__Lynx__) && defined(__x86__)) #define BYTE_ORDER LITTLE_ENDIAN #endif #if defined(sel) || defined(pyr) || defined(mc68000) || defined(sparc) || \ defined(is68k) || defined(tahoe) || defined(ibm032) || defined(ibm370) || \ defined(MIPSEB) || defined(_MIPSEB) || defined(_IBMR2) || defined(DGUX) ||\ defined(apollo) || defined(__convex__) || defined(_CRAY) || \ defined(__hppa) || defined(__hp9000) || \ defined(__hp9000s300) || defined(__hp9000s700) || \ defined(__hp3000s900) || defined(__hpux) || defined(MPE) || \ defined (BIT_ZERO_ON_LEFT) || defined(m68k) || defined(__sparc) || \ (defined(__Lynx__) && \ (defined(__68k__) || defined(__sparc__) || defined(__powerpc__))) #define BYTE_ORDER BIG_ENDIAN #endif ``` 由於 C 標準裡面沒有定義跟判斷 byte order 相關的方法,所以要在編譯時期找出 byte order 也只能用像是這種直接列表列出來的方法。如果編譯器是用 GCC 的話則有提供 `__BYTE_ORDER__` 這個 Macro 來判斷。 ## 3-3 前兩行 `x ^= x >> 1;` 和 `x ^= x >> 2;` 運算後會將每一個 bit 與其前面三個 bit 做 xor , `0x11111111` 在二進位表示的時候是 `0b1_0001_0001_0001_0001_0001_0001_0001` ,與 x 做 and 即為每四個 bit 中取最低的 bit,之後再與 `0b1_0001_0001_0001_0001_0001_0001_0001` 相乘,在最高 bit 往下數四個 bit 那位剛好是將所有位數相加的結果,因此在 32 bit 的時候平移 28 位之後做 and 1 取得那位數即為將最初 x 值中二進位每四個位數做 xor 後再把它們相加的結果,也是 parity check 的結果