# 2018q3 Homework3 (review) contributed by < [`letticee`](https://github.com/letticee) > ## 第 1 週測驗題 ## 第 2 週測驗題 ### 測驗 `1` 考慮到以下程式碼: ```C const char *p; void dont_do_this(void) { const char c_str[] = "This will change"; p = c_str; } ``` 指出存在的問題和提出修正機制,需要用 C99/C11 規格解釋。 :::success 延伸問題: 在 Common Vulnerabilities and Exposure](https://cve.mitre.org/) (CVE) 找出類似上述不當的 string literal 操作,而導致的安全漏洞,並加以探討 ::: --- * 如果在一個物件的 lifetime 外去存取他,是 undefined behavior。當指標指到的物件達到他的 lifetime 時,指標所指到的值變得不確定 * C99 [6.2.4] * If an object is referred to outside of its lifetime, the behavior is undefined. The value of a pointer becomes indeterminate when the object it points to reaches the end of its lifetime. * 問題:在 dont_do_this() 函式內,將 p 指到了 c_str 的位址。但是 c_str 的 lifetime 只在 dont_do_this() 函式內,函式結束後, p 還是指向原先的 c_str,如果對 p 就是一個 undefined behavior * [類似的操作](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-7906): * CVE-ID CVE-2014-7906 * Description Use-after-free vulnerability in the Pepper plugins in Google Chrome before 39.0.2171.65 allows remote attackers to cause a denial of service or possibly have unspecified other impact via crafted Flash content that triggers an attempted PepperMediaDeviceManager access outside of the object's lifetime. 39.0.2171.65 版本之前的 Google Chrome ,Pepper 插件中有漏洞,遠端攻擊者可以透過設計過的 Flash 觸發 attempted PepperMediaDeviceManager,嘗試在物件的 lifetime 外去存取,導致拒絕服務或是其他可能造成其他未指定的影響 ### *測驗 `3` Linux 核心程式碼 [include/linux/list.h](https://github.com/torvalds/linux/blob/master/include/linux/list.h) 提到以下程式碼,為何每個 `head` 使用時都要先加上 `()` 呢? ```C #define list_for_each_prev(pos, head) \ for (pos = (head)->prev; pos != (head); pos = pos->prev) ``` :::success 延伸問題: 在 Linux 核心原始程式碼找出類似上述「走訪節點」的片段,討論其實作技巧和考量點 ::: --- * `()` 內的內容會優先執行,在 `head` 加上 `()` 是為了避免 macro 展開後 `head` 取到的不是預期的內容 * e.g. 在 sound/core/device.c 中使用到 list_for_each_prev 的部分 ```C /* insert the entry in an incrementally sorted list */ list_for_each_prev(p, &card->devices) { struct snd_device *pdev = list_entry(p, struct snd_device, list); if ((unsigned int)pdev->type <= (unsigned int)type) break; } ``` 若是沒有在 `head` 加上 `()` 的話,macro 中的 `head` 以 `&card->devices` 代入展開會變成 `&(card->devices->prev)` * 參考 * [#define](https://doc.bccnsoft.com/docs/cppreference_en/preprocessor/all.html) * [C Programming/Preprocessor directives and macros - #define](https://en.wikibooks.org/wiki/C_Programming/Preprocessor_directives_and_macros##define) * [Use parentheses within macros around parameter names](https://wiki.sei.cmu.edu/confluence/display/c/PRE01-C.+Use+parentheses+within+macros+around+parameter+names) * 思考 :::warning * `pos->prev` 的 `pos` 不加 `()` 是因為在這個 for 中已經先執行 `pos = (head)->prev` ,所以在 `pos` 不會出現這個問題? ::: ### 測驗 `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 語言程式碼,並加以討論 ::: --- * ==傳給 func 的參數 p 是 ptrA (指到 A 的指標)的副本,他的 lifetime 只在函式裡({ p = &B; }),ptrA 內含值其實是沒更動到的。== * ==修改為:將傳給 func 的參數改為一個指到 ptrA 的指標(A 的指標的指標)的副本,藉由這個指標 &ptrA 去改變 ptrA 所指到的位址== ```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; } ``` * the pointer to the pointer 的 C 語言程式碼 [source](https://github.com/Bilibili/ijkplayer/blob/cced91e3ae3730f5c63f3605b00d25eafcf5b97b/ijkmedia/ijkplayer/ff_ffplay.c#L4211) ```clike int ffp_get_audio_codec_info(FFPlayer *ffp, char **codec_info) { if (!codec_info) return -1; // FIXME: not thread-safe if (ffp->audio_codec_info) { *codec_info = strdup(ffp->audio_codec_info); } else { *codec_info = NULL; } return 0; } ``` 傳入 codec_info 的指標的指標到函式中,在函式中去操作 codec_info 的指標,將他指到複製出來的字串。 --- ### 測驗 `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 語言規格書解說。 --- * 一個有 returning type 的 function designator 會被轉為 pointer to function returning type (除非他是 `sizeof` 或 `&` 運算子) * 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’’. * 對 fuction 使用 `*` operator 操作時,結果會是 function designator。對 pointer to type 使用 `*` operator 操作的話,結果會是 type * C99 [6.5.3.2] The unary `*` operator denotes indirection. If the operand points to a function, the result is a function designator; if it points to an object, the result is an lvalue designating the object. If the operand has type ‘‘pointer to type’’, the result has type ‘‘type’’. If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined. * `&*E` 等於 E,且當 E 是一個 function designator 時,`*&E` 等於 E * C99 [6.5.3.2] Thus, `&*E` is equivalent to `E` (even if E is a null pointer), and &(E1[E2]) to ((E1)+(E2)). It is always true that if E is a function designator or an lvalue that is a valid operand of the unary `&` operator, `*&E` is a function designator or an lvalue equal to `E`. If `*P` is an lvalue and `T` is the name of an object pointer type, `*(T)P` is an lvalue that has a type compatible with that to which T points. * e.g. 宣告一個指標名為 `fptr` ,指向 `int (int, int)` 我們可以用以下兩種方式將已知的function, assign 給 function pointer: 1. `fptr` = `&func_name`; 2. `fptr` = `func_name`; int func(int, int); int (*fptr)(int, int); type of `fptr` is : `int (*)(int, int)`; type of `func` is : `int (int, int)`; type of `&func` is : `int (*)(int , int)`; * fptr = &func 很合理,型別正確並且數值也正確(&func 數值和 func 一樣) * 可是 fptr = func 型別不一樣。 所以在這裡其實做了 implicit conversion 將 function designator 轉成 pointer to function 使用 * 參考 * [C pointer to function 函式指標 學習心得 - 2. 如何宣告一個函式指標](http://www.programmer-club.com.tw/ShowSameTitleN/c/39038.html) * [function designator 相關討論](http://www.programmer-club.com.tw/ShowSameTitleN/c/39038.html) * `puts()` 是個 function designator,會被轉成 pointer to function * `*put()` 加上 dereference of 讓 pointer to function 轉成 function designator,但會再被轉成 pointer to function * `&put()` 加上 address of 讓原本的 function designator 轉成 pointer to function * 所以題目的三個例子,最後都是轉變成 pointer to function * ```C #include <stdio.h> int main() { return (&&puts)("Hello"); } ``` 就會出現 error 了 ``` week2Q5.c: In function ‘main’: week2Q5.c:2:22: error: lvalue required as unary ‘&’ operand int main() { return (&(&puts))("Hello"); } ^ ``` ## 第 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 ``` 倘若將第 10 和第 11 換為以下: ```C=10 printf("Address of t.x is %p", &t.x); ``` 會發生什麼事? ==作答區== * `(a)` 印出類似 `0x7ffd144e8ad4` 的輸出,也就是 `t` 結構物件的位址; * `(b)` 印出類似 `0x7ffd144e8ad4` 的輸出,和 `t` 結構物件差距 4 bytes; * `(c)` 可以印出數值,但這與編譯器實作有關,不能解讀; * `(d)` 編譯失敗,不能這樣宣告結構體的成員; * `(e)` 編譯失敗,不能將指標指向沒有對齊 1 byte 的結構體成員; 參考資料: [Portable Bit Fields in packetC](https://link.springer.com/content/pdf/10.1007/978-1-4302-4159-1_34.pdf) :::success 延伸問題: 解釋原因,並且找出 C99/C11 規格書對應的描述 ::: --- * `(e)` 編譯失敗,不能將指標指向沒有對齊 1 byte 的結構體成員; * 實驗 ``` week3Q1.c: In function ‘main’: week3Q1.c:11:67: error: cannot take address of bit-field ‘x’ printf("Offset of x in struct test is %ld\n", (uintptr_t) &t.x - (uintptr_t) &t); ^ ``` * 因為不能對 bit-field 物件使用 & (address-of) 操作 * C99 [6.7.2.1] 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. * 在 struct 中,non-bit-field的成員會根據他們所宣告的增加地址 * C99 [6.7.2.1] Within a structure object, the non-bit-field members and the units in which bit-fields reside have addresses that increase in the order in which they are declared. A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning. * 所以 z 會按照 unsigned int 的大小(4 bytes)對齊 * bit-field 間的排列 * C99 [6.7.2.1] 8 A member of a structure or union may have any object type other than a variably modified type. In addition, a member may be declared to consist of a specified number of bits (including a sign bit, if any). Such a member is called a bit-field; its width is preceded by a colon. 10 An implementation may allocate any addressable storage unit large enough to hold a bit- field. 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. The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined. The alignment of the addressable storage unit is unspecified. * 兩個 bit-field ,若有足夠空間,則第二個緊鄰第一個。若沒有足夠空間,則第二個 bit-field 要放到下一個 unit 或是橫跨兩個 unit 是由 compiler 的實作方法決定 --- ### 測驗 `2`