# # 2018q3 Homework3 (Review) 課前測驗參考解答: Q2 ###### tags: `GOGOGOGOGOGOG` ### 測驗 `2` ```C void (*signal(int sig, void (*handler)(int))) (int); ``` 該如何解析呢? 提示: 參閱 manpage: [signal(2)](http://man7.org/linux/man-pages/man2/signal.2.html) :::success 延伸問題: 解釋 signal(2) 的作用,並在 GitHub 找出應用案例 ::: 在了解signal(2)之前,必須先了解上述的程式碼是在表達什麼?底下是我在網路上找到的範例程式,充分說明了這段程式碼的應用: ``` #include <stdio.h> // First function that could be returned by our principal function // Note that we can point to it using void (*f)(int) void car_is_red(int color) { printf("[car_is_red] Color %d (red) is my favorite color too !\n", color); } // Second function that could be returned by our principal function // Note that we can point to it using void (*f)(int) void car_is_gray(int color) { printf("[car_is_gray] I don't like the color %d (gray) either !\n", color); } // The function taken as second parameter by our principal function // Note that we can point to it using void (*func)(int) void show_car_type(int mod) { printf("[show_car_type] Our car has the type: %d\n",mod); } /* Our principal function. Takes two parameters, returns a function. */ void (* show_car_attributes(int color, void (*func)(int)) )(int) { printf("[show_car_attributes] Our car has the color: %d\n",color); // Use the first parameter int mod = 11; // Some local variable of our function show_car_attributes() func(mod); // Call the function pointed by the second parameter (i.e. show_car_type() ) // Depending on color value, return the pointer to one of two functions // Note that we do NOT use braces with function names if (color == 1) return car_is_red; else return car_is_gray; } //main() function int main() { int color = 2; // Declare our color for the car void (*f)(int); // Declare a pointer to a function with one parameter (int) f = show_car_attributes(color, show_car_type); // f will take the return // value of our principal function. Stated without braces, the // parameter "show_car_types" is a function pointer of type // void (*func)(int). f(color); // Call function that was returned by show_car_attributes() return 0; } ``` 首先透過gdb看到一開始程是在執行時,首先進入main主函式中,在第三行中呼叫show_car_attributes將color和show_car_type副函式傳入裡面讓它接收,接著進入該副函式,其中*func代表指向show_car_type函式。將int mod寫入該函式中,並且去做show_car_type裡做的事,做完後回到主程式,接下來是比較複雜的部份:在void (* show_car_attributes(int color, void (*func)(int)) )(int) 我的解讀是為一個指標其指向的是函式其回傳為一個函式內含一個int參數,並且接收(傳入)的參數分別為一int parameter和一指標指向函式其接收一int參數。 也就是說在 f = show_car_attributes;中 f會接收該函式的回傳值,也就是car_is_red或者是car_is_gray,藉由color傳入后後是印出car_is_gray。 ### signal 宣告方式與定義: SYNOPSIS #include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); DESCRIPTION The behavior of signal() varies across UNIX versions, and has also var‐ ied historically across different versions of Linux. Avoid its use: use sigaction(2) instead. See Portability below. signal() sets the disposition of the signal signum to handler, which is either SIG_IGN, SIG_DFL, or the address of a programmer-defined func‐ tion (a "signal handler"). If the signal signum is delivered to the process, then one of the fol‐ lowing happens: * If the disposition is set to SIG_IGN, then the signal is ignored. * If the disposition is set to SIG_DFL, then the default action asso‐ ciated with the signal (see signal(7)) occurs. * If the disposition is set to a function, then first either the dis‐ position is reset to SIG_DFL, or the signal is blocked (see Porta‐ bility below), and then handler is called with argument signum. If invocation of the handler caused the signal to be blocked, then the signal is unblocked upon return from the handler. The signals SIGKILL and SIGSTOP cannot be caught or ignored. 上面的意思是在調用中,參數signum指出要設置處理方法的信號。第二個參數handler是一個處理函式(處理的方式),或者是: SIG_IGN:忽略參數signum所指的信號。 SIG_DFL:恢復參數signum所指信號的處理方法為預設值。 而收到信號的進程對各種信號有不同的處理方法。處理方法可以分為三類:第一種是類似中斷的處理常式,對於需要處理的信號,進程可以指定處理函式,由該函式來處理。第二種方法是,忽略某個信號,對該信號不做任何處理,就像未發生過一樣(SIG_IGN)。第三種方法是,對該信號的處理保留系統的預設值(SIG_DFL) 其中信號SIGKILL和SIGSTOP既不能被捕捉,也不能被忽略。信號SIGIOT與SIGABRT是一個信號。可以看出,同一個信號在不同的系統中值可能不一樣,所以建議最好使用為信號定義的名字,而不要直接使用信號的值。 在終端機輸入kill -l 會出現信號的種類,這裡只列出19個(共64個) 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 其實依照分類,我們可以按照信號發出的原因進行七種分類: (1) 與進程終止相關的信號。當進程退出,或者子進程終止時,發出這類信號。 (2) 與進程例外事件相關的信號。如進程越界,或企圖寫一個唯讀的記憶體區域(如程式正文區),或執行一個特權指令及其他各種硬體錯誤。 (3) 與在系統調用期間遇到不可恢復條件相關的信號。如執行系統調用exec時,原有資源已經釋放,而目前系統資源又已經耗盡。 (4) 與執行系統調用時遇到非預測錯誤條件相關的信號。如執行一個並不存在的系統調用。 (5) 在使用者態下的進程發出的信號。如進程調用系統調用kill向其他進程發送信號。 (6) 與終端交互相關的信號。如使用者關閉一個終端,或按下break鍵等情況。 (7) 跟蹤進程執行的信號。 底下是我在github中找到的opensource其內容是關於 `SIGHUP ` `SIGUSR1 ` `SIGKILL ` `SIGINT ` 的使用,例如:`SIGINT ` 就是指按下鍵盤上的ctrl+c就會進入中斷等等..... 參考網址:https://gist.github.com/aspyct/3462238 ### 2018q3 第 3 週測驗題 #### 測驗 `2` 考慮以下程式碼,在 little-endian 硬體上,會返回 `1`,反之,在 big-endian 硬體上會返回 `0`: ```C int main() { union { int a; char b; } c = { .a = K1 }; return c.b == K2; } ``` 補完上述程式碼。 ==作答區== K1 = ? * `(a)` 0 * `(b)` 1 * `(c)` -1 * `(d)` 254 K2 = ? * `(a)` 0 * `(b)` 1 * `(c)` 254 :::success 延伸問題: 解釋運作原理,並找出類似的程式碼 ::: 首先我們要先了解什麼是little-endian 硬體和什麼是big-endian 硬體上,在目前的cpu中最常見的位元組順序有兩種,分別是 Big-Endian 與 Little-Endian,Big-Endian 是指資料放進記憶體中的時候,最高位的位元組會放在最低的記憶體位址上,而 Little-Endian 則是剛好相反,它會把最高位的位元組放在最高的記憶體位址上。 例如以一個程式碼的例子來說明: ``` #include <stdio.h> typedef union { unsigned long l; unsigned char c[4]; } EndianTest; int main() { EndianTest et; et.l = 0x12345678; printf("本系統位元組順序為:"); if (et.c[0] == 0x78 && et.c[1] == 0x56 && et.c[2] == 0x34 && et.c[3] == 0x12) { printf("Little Endian\n"); } else if (et.c[0] == 0x12 && et.c[1] == 0x34 && et.c[2] == 0x56 && et.c[3] == 0x78) { printf("Big Endian\n"); } else { printf("Unknown Endian\n"); } printf("0x%lX 在記憶體中的儲存順序:\n", et.l); for (int i = 0; i < 4; i++) { printf("%p : 0x%02X\n", &et.c[i], et.c[i]); } return 0; } ``` 假設我們有一個 32 位元(bits)的整數資料為 0x12345678,在 Big-Endian 的機器將其放置在記憶體中的時候,會按照下圖中的規則放置: ![](https://i.imgur.com/yI2mR2E.png) 如果換成 Little-Endian 的機器,將 0x12345678 放在記憶體中的方式就會剛好相反,會變成這樣: ![](https://i.imgur.com/cNoGQNc.png) 而本次程式碼,若是以char存取,在Little-Endian中會取成0x01,若為Big-Endian則為0x00 ### 測驗 `4` 考慮到以下程式碼: ```clike int B = 2; void func(int *p) { p = &B; } int main() { int A = 1, C = 3; int *ptrA = &A; func(ptrA); printf("%d\n", *ptrA); return 0; } ``` ![](https://i.imgur.com/30cSCdC.jpg) 該如何修改,才能讓 `func` 得以變更到 `main` 裡頭 `ptrA` 的內含值? :::success 延伸問題: 在 GitHub 找出使用 the pointer to the pointer 的 C 語言程式碼,並加以討論 ::: github中的pointer to pointer :https://gist.github.com/strager/c15581719d7cad8ffe7a ``` #include <stdio.h> int main(int argc, char **argv) { int x = 1; int y = 2; int *xp = &x; int *yp = &y; printf("1: xp = %p; *xp = %d\n", xp, *xp); printf("2: yp = %p; *xp = %d\n", yp, *yp); int *zp = NULL; int **pp = &zp; printf("3: pp = %p; *pp = %p; zp = %p\n", pp, *pp, zp); *pp = xp; printf("4: pp = %p; *pp = %p; **pp = %d; zp = %p\n", pp, *pp, **pp, zp); *pp = yp; printf("5: pp = %p; *pp = %p; **pp = %d; zp = %p\n", pp, *pp, **pp, zp); } ``` 在`main(int argc, char **argv) ` 中若需要將執行程式時,使用者所輸入的命令列參數讀取進來,則可在 main 函數中加上 argc 與 argv 兩個參數,有些人會把 *argv[] 寫成 **argv 這種不同的寫法,但意思其實是一樣的。 重點是在程式的第八行開始: 首先宣告一個指標變數zp和一個指標的指標pp,其中zp的值為NULL,而pp的值為zp的位址。印出後的結果為: ``` 1: xp = 0x7ffe7ac06ab0; *xp = 1 2: yp = 0x7ffe7ac06ab4; *xp = 2 3: pp = 0x7ffe7ac06ab8; *pp = (nil); zp = (nil) ``` 再來是 `*pp=xp; `將xp的值(也就是x的位址)寫入pp中,同時因為*pp = zp;所以zp的儲存值由NULL變為xp的值,於是印出的結果為: ``` 4: pp = 0x7ffe7ac06ab8; *pp = 0x7ffe7ac06ab0; **pp = 1; zp = 0x7ffe7ac06ab0 ``` 而yp也是相同結果,之後會再補上圖形進行說明。 ### 測驗 `5` 以下程式是合法 C99 程式嗎? ```C #include <stdio.h> int main() { return (********puts)("Hello"); } ``` 請搭配 C 語言規格書解釋 繼續思考以下是否合法: ```C #include <stdio.h> int main() { return (**&puts)("Hello"); } ``` 繼續變形: ```C #include <stdio.h> int main() { return (&**&puts)("Hello"); } ``` 也會合法嗎?為什麼?請翻閱 C 語言規格書解說。 :::success 是合法的 ::: 首先先看一段c99程式規格書的敘述: ``` C99 [6.3.2.1] A function designator is an expression that has function type. Except when it is the operand of the sizeof operator or the unary & operator, a function designator with type ‘‘function returning type ’’ is converted to an expression that has type ‘‘pointer to function returning type ``` 以及 ``` A pointer to void may be converted to or from a pointer to any incomplete or objecttype. A pointer to any incomplete or object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer ``` 從上面的signal和c99我們可以得知,一個pointer to function returning type常常拿來接收一個function designator。例如:`void (* show_car_attributes(int color, void (*func)(int)) )(int) ` 中的`void (*func)(int)) )(int) `便是接收show_car_type函式的pointer to function。puts是個function designator,在轉入其他函式或return時會被改為pointer to function。不論怎麼加 `* ` 或是`& ` 都是轉為pointer to function 所以為合法的c語言程式宣告。 ### 測驗 `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 ``` 倘若將第 10 和第 11 換為以下: ```C=10 printf("Address of t.x is %p", &t.x); ``` 會發生什麼事? :::danger 編譯失敗,不能將指標指向沒有對齊 1 byte 的結構體成員 ::: 對照c語言規格書中的敘述 ``` 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. ``` 以下是我實驗的程式碼,可以看到在編譯過程中,系統就會告訴我們 :::danger can not take address of bit fields ::: 透過參考老師給的資料後,其中有一段敘述提到 :::info C99 3.2 alignment requirement that objects of a particular type be located on storage boundaries with addresses that are particular multiples of a byte address ::: 以下是我測試的程式碼: ``` #include <stdio.h> #include <string.h> /* define simple structure */ struct { unsigned int a; unsigned int b; } status1; /* define a structure with bit fields */ typedef struct { unsigned int a : 1; unsigned int b : 5; } status2; int main() { printf("Memory size occupied by status1 : %d\n", sizeof(status1)); // 8 printf("Memory size occupied by status2 : %d\n", sizeof(status2)); // 4 status2 s; s.b = 7; s.b = 8; // error! printf("Address of s.a is %p", &s.a); printf("Address of s.b is %p", &s.b); return 0; } ```