# 2018q3 Homework3 (review) contributed by <[pjchiou](https://github.com/pjchiou)> --- ## Week2 - #2 指標篇 提到 signal 系統呼叫的原型宣告: ```cpp void (*signal(int sig, void (*handler)(int))) (int); ``` |Declaration|Description| |:-|:-| |==signal()==|signal is a function| |signal(==int sig, void (*handler)(int)==)|with 2 arguments| |signal(==int sig==, void (*handler)(int) )|the first argument is a int `sig`| |signal(int sig, ==void (*handler)(int)== )|the second argument is a function pointer `handler`| |signal(int sig, ==void== (*handler)(==int==) )|handler points to a function with 1 argument int and returns void| |==void (*signal==(int sig, void (*handler)(int)))==(int)==;|signal returns a function pointer points to a function with 1 argument int and returns void.| - signal is a function with 2 arguments. - the first argument is a int `sig` - the second argument is a function pointer `handler` - handler needs 1 argument int and return void. - signal returns a function pointer points to a function with 1 argument int and returns void. 這個函式的使用在我們的第 2 個作業內的程式碼 qtest.c 就有。 ```cpp static void queue_init() { fail_count = 0; q = NULL; signal(SIGSEGV, sigsegvhandler); signal(SIGALRM, sigalrmhandler); } ``` 會在程式執行時期若接收到 - SIGSEGV 訊號就執行 `segsegvhandler` 函式 - SIGALRM 訊號就執行 `signalrmhandler` 函式 而這兩個函式是自定義的 exception handler 。 --- ## Week2 - #6 考慮以下程式碼: ```cpp int **arr = malloc(3 * sizeof(int *)); for (int i = 0; i < 3; i++) arr[i] = malloc(4 * sizeof(int)); ``` 如果要 free() 時,是否需要兩層的迴圈呢?倘若有人只是 free(arr); 會發生什麼事?又,該如何改善? 這段程式如果畫成圖長這個樣子 ```graphviz digraph malloc{ node[shape=record] arr [label="arr", shape=plaintext] arr_a [label="<ptr0> arr[0]|<ptr1> arr[1]|<ptr2> arr[2]"] data1 [label="<ptr1>|||"] data2 [label="<ptr2>|||"] data3 [label="<ptr3>|||"] arr -> arr_a:ptr0:nw arr_a:ptr0 -> data1:ptr1:nw arr_a:ptr1 -> data2:ptr2:nw arr_a:ptr2 -> data3:ptr3:nw } ``` 可以看出如果只 free(arr) 的話,將會造成 memory leak 。 我們用以下指令進行編繹後測試的結果 ``` clang -O0 -g -fsanitize=address -fno-omit-frame-pointer free_ori.c ``` ```cpp=1 #include <stdio.h> #include <stdlib.h> int main() { int **arr = malloc(3 * sizeof(int *)); for (int i = 0; i < 3; i++) arr[i] = malloc(4 * sizeof(int)); /*for (int i = 0; i < 3; i++) free(arr[i]);*/ free(arr); return (0); } ``` 故意將 #9~10 的內層 free 拿掉,出現以下訊息 :::danger ================================================================= \==9692==ERROR: LeakSanitizer: detected memory leaks Direct leak of 48 byte(s) in 3 object(s) allocated from: #0 0x4d9bd0 in malloc (/home/pjchiou/builder/a.out+0x4d9bd0) #1 0x512120 in main /home/pjchiou/builder/free_ori.c:7:14 #2 0x7f481c340b96 in __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310 SUMMARY: AddressSanitizer: 48 byte(s) leaked in 3 allocation(s). ::: 跟預期的一樣,改成兩層 free 後就沒有 ERROR 出現。 ### 改善 我們既然已經知道我們要的空間是 3*4 個 int ,不如一開始就 malloc 足夠的空間,再用指標去指向我們要的位置。 參考以下程式 ```cpp=1 #include <stdio.h> #include <stdlib.h> int main() { int *p = malloc(sizeof(int) * 3 * 4), *matrix[3]; for (int i = 0; i < 3; i++) matrix[i] = p + i * 4; free(*matrix); return (0); } ``` 這段程式畫成圖長這樣 ```graphviz digraph malloc{ node[shape=record] arr [label="p", shape=plaintext] arr_a [label="<ptr0> matrix[0]|<ptr1> matrix[1]|<ptr2> matrix[2]"] data [label="<ptr0>||||<ptr1>||||<ptr2>|||"] matptr [label="matrix", shape=plaintext] arr -> data:ptr0:nw arr_a:ptr0 -> data:ptr0:nw arr_a:ptr1 -> data:ptr1:nw arr_a:ptr2 -> data:ptr2:nw matptr -> arr_a:ptr0:nw } ``` 如此一來只需要 free 一次,可以減少 malloc 時 meta data 的使用。 :::info 或許利用前置處理器可以將其擴展成==實際上還是連續記憶體的 k 維陣列==,但我還在想要怎麼寫。 ::: --- ## Week3 - #1 考慮以下程式碼: ```cpp #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 ``` 倘若將第 10 和第 11 換為以下: printf("Address of t.x is %p", &t.x); 會發生什麼事? 這違反了 [C11](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1548.pdf) 中 6.5.3.2 節 Address and indirection operators 的限制,這個限制提到 :::warning 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. ::: 以下兩種==不能==用 `&` 運算子 - bit-field - declared with register-storage specifier --- ## Week3 - #2 考慮以下程式碼,在 little-endian 硬體上,會返回 1,反之,在 big-endian 硬體上會返回 0: ```cpp int main() { union { int a; char b; } c = { .a = 1 }; return c.b == 1; } ``` ### 原理 假設有個佔 4 bytes 的整數 `0x12345678` ,在 little-endian 與 big-endian 的硬體上,分別會是這麼存 ```graphviz digraph { node[shape=record] littleptr [label="little-endian", shape=plaintext] little [label="0x78|0x56|0x34|0x12"] bigptr [label="big-endian", shape=plaintext] big [label="0x12|0x34|0x56|0x78"] } ``` 兩者完全相反,所以在上述的變數在記憶體內會是 ```graphviz digraph { node[shape=record] littleptr [label="little-endian", shape=plaintext] little [label="0x01|0x00|0x00|0x00"] bigptr [label="big-endian", shape=plaintext] big [label="0x00|0x00|0x00|0x01"] } ``` 而如果用 `char` 存取,只會取 1 個 byte ,在 little-endian 下會取到 0x01 ; 反之會取到 0x00 。 類似的程式在判斷硬體是 little-endian 或 big-endian 的時候有很多,例如 - [C program to check little vs. big endian](https://stackoverflow.com/questions/12791864/c-program-to-check-little-vs-big-endian) - [What is Big Endian and Little Endian? How to detect it in C?](http://www.equestionanswers.com/c/c-big-little-endian.php) - [google](https://www.google.com.tw/search?client=ubuntu&hs=F0Z&ei=HwW5W_nyOsjW8QWWnaRI&q=c+endian+check&oq=c+endian+check&gs_l=psy-ab.3..0i7i30i19k1j0i19k1j0i8i30i19k1l8.6677.6755.0.6912.2.2.0.0.0.0.75.149.2.2.0....0...1c.1.64.psy-ab..0.2.149...0i8i7i30i19k1.0.csHdVQO2jtY) --- ## Week3 - #4 考慮以下程式碼: ```cpp=1 #include <stdlib.h> #include <string.h> typedef struct rec { char *value; struct rec *next; } record; void insert_sorted(record **r, const char *value) { record *newrec = NULL; while (*r && strcmp(value, (*r)->value) > 0) r = &((*r)->next); newrec = malloc(sizeof(record)); newrec->value = strdup(value); newrec->next = *r; *r = newrec; } ``` 函式 insert_sorted 的作用是在 r 所指向已依據數值排序的 linked list 中,安插給定的字串,考慮到 string literal 的生命週期,應該在新建立的節點中,複製給定的字串。 我們來看這個程式畫成圖的樣子,假設 linked list 內已有 `aa`->`bb`->`dd` ,現在要插入 `cc` ```graphviz digraph insert{ node[shape=record] aa [label="<value> aa|<ptr> next=bb"] bb [label="<value> bb|<ptr> next=dd"] dd [label="<value> dd|<ptr> next=NULL"] cc [label="<value> cc|<ptr> next=NULL"] r [label="r", shape=plaintext] aa:ptr -> bb:value:nw bb:ptr -> dd:value:nw r -> bb:ptr:nw } ``` 第 16 行,把 `cc` 的 next 值改為 *r ```graphviz digraph insert{ node[shape=record] aa [label="<value> aa|<ptr> next=bb"] bb [label="<value> bb|<ptr> next=dd"] dd [label="<value> dd|<ptr> next=NULL"] cc [label="<value> cc|<ptr> next=dd"] r [label="r", shape=plaintext] aa:ptr -> bb:value:nw bb:ptr -> dd:value:nw cc:ptr -> dd:value:nw r -> bb:ptr:nw } ``` 第 17 行,把`*r` 也就是 `bb` 的 next 值改為 `cc` ```graphviz digraph insert{ node[shape=record] aa [label="<value> aa|<ptr> next=bb"] bb [label="<value> bb|<ptr> next=cc"] dd [label="<value> dd|<ptr> next=NULL"] cc [label="<value> cc|<ptr> next=dd"] r [label="r", shape=plaintext] aa:ptr -> bb:value:nw bb:ptr -> cc:value:nw cc:ptr -> dd:value:nw r -> bb:ptr:nw } ``` 注意 `r` 指向的位置,與我們常見的方法不一樣(雖然效果是等價的)。 ### 刪除節點的程式 ```cpp=1 void delete_node(record **r, const char *value) { if (!value) { printf("Can not delete a NULL in list.\n"); return; } while (*r && strcmp(value, (*r)->value) > 0) r = &((*r)->next); if (!(*r) || strcmp(value, (*r)->value) != 0) { printf("%s is not in this list.\n", value); return; } record *ptr = *r; *r = (*r)->next; free(ptr->value); free(ptr); } ``` - 因為這是一個排序過的 linked list 所以在 #8 中,我們不用看過全部的 list 就知道有沒有在其中。 ### 蒐尋節點 ```cpp=1 void search_node(record *r, const char *value) { if (!value) { printf("NULL is not in this list.\n"); return; } int i; for (i = 1; r && strcmp(value, r->value) > 0; i++) r = r->next; if (r && strcmp(value, r->value) == 0) printf("%s is at %d position.\n", value, i); else printf("Can not find %s in this list.\n", value); } ``` [測試程式](https://github.com/pjchiou/list) :::info 如果還有時間,再把它改成像作業二的測試方式,可支援自動測試。 :::