# 2018q3 Homework3 (review)
contributed by < [`jason53415`](https://github.com/jason53415) >
## 第 2 週測驗題 測驗 `3`
:::info
Linux 核心程式碼 include/linux/list.h 提到以下程式碼,為何每個 head 使用時都要先加上 () 呢?
```c
#define list_for_each_prev(pos, head) \
for (pos = (head)->prev; pos != (head); pos = pos->prev)
```
:::
* 因為這邊使用了 macro 來定義函式,會在 pre-process 時直接在程式碼之中展開,因此如果 head 是一個表示式或包含了其他優先權較低的 operator,且又沒有加上 `()` 時,就會出現程式行為不符合預期的情況。
* 以下嘗試如果 `head` 不加 `()` 會發生什麼錯誤:
```C=
#include <stdio.h>
#define list_for_each_prev(pos, head) \
for (pos = head->prev; pos != head; pos = pos->prev)
struct list_head {
struct list_head *prev;
struct list_head *next;
};
int main() {
struct list_head head, node;
struct list_head *ptr;
head.prev = &node;
head.next = &node;
node.prev = &head;
node.next = &head;
list_for_each_prev(ptr, &head)
printf("%p %p\n", ptr, &head);
}
```
* 編譯時便會產生錯誤:
```shell=
$ gcc quiz2-3.c -o quiz2-3
quiz2-3.c: In function ‘main’:
quiz2-3.c:3:20: error: invalid type argument of ‘->’ (have ‘struct list_head’)
for (pos = head->prev; pos != head; pos = pos->prev)
^
quiz2-3.c:17:5: note: in expansion of macro ‘list_for_each_prev’
list_for_each_prev(ptr, &head)
^~~~~~~~~~~~~~~~~~
```
* 加上 `()` 之後就沒有錯誤了:
```shell=
$ head quiz2-3.c -n 3
#include <stdio.h>
#define list_for_each_prev(pos, head) \
for (pos = (head)->prev; pos != (head); pos = pos->prev)
$ gcc quiz2-3.c -o quiz2-3
$ ./quiz2-3
0x7ffe70fd7ec0 0x7ffe70fd7eb0
```
:::success
延伸問題: 在 Linux 核心原始程式碼找出類似上述「走訪節點」的片段,討論其實作技巧和考量點
:::
* 以下為 kernel/sched.c 中所定義的一個函式 `__wake_up_common()`,作用是依照目前的需求來喚醒 wait queue 中的 task。
```C=
/*
* The core wakeup function. Non-exclusive wakeups (nr_exclusive == 0) just
* wake everything up. If it's an exclusive wakeup (nr_exclusive == small +ve
* number) then we wake all the non-exclusive tasks and one exclusive task.
*
* There are circumstances in which we can try to wake a task which has already
* started to run but is not in state TASK_RUNNING. try_to_wake_up() returns
* zero in this (rare) case, and we handle it by continuing to scan the queue.
*/
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, int wake_flags, void *key)
{
wait_queue_t *curr, *next;
list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
unsigned flags = curr->flags;
if (curr->func(curr, mode, wake_flags, key) &&
(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
break;
}
}
```
* 其中第 15 行的 `list_for_each_entry_safe()` 就是定義在 list.h 中,可以用來走訪所有節點中某個 member 的函式。
```c=
/**
* list_for_each_entry_safe - iterate over list entries and allow deletes
* @entry: pointer used as iterator
* @safe: @type pointer used to store info for next entry in list
* @head: pointer to the head of the list
* @member: name of the list_head member variable in struct type of @entry
*
* The current node (iterator) is allowed to be removed from the list. Any
* other modifications to the the list will cause undefined behavior.
*
* FIXME: remove dependency of __typeof__ extension
*/
#define list_for_each_entry_safe(entry, safe, head, member) \
for (entry = list_entry((head)->next, __typeof__(*entry), member), \
safe = list_entry(entry->member.next, __typeof__(*entry), member); \
&entry->member != (head); entry = safe, \
safe = list_entry(safe->member.next, __typeof__(*entry), member))
```
## 第 2 週測驗題 測驗 `5`
:::info
以下程式是合法 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 語言規格書解說。
:::
* 實際執行結果:
```shell=
$ cat quiz2-5.c
#include <stdio.h>
int main() { return (********puts)("Hello"); }
$ gcc quiz2-5.c -o quiz2-5
$ ./quiz2-5
Hello
```
* 查閱 C99 規格書可以得到以下的解釋:
> **§6.5.2.2 Function calls**
>
> 1) The expression that denotes the called function shall have type pointer to function returning void or returning an object type other than an array type.
> **§6.3.2.1 Lvalues, arrays, and function designators**
>
> 4) 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*’’.
* 由此可知一個為 function returning type 的 function designator 實際上在處理時會被轉為 pointer to function returning type。
* 因為 `put()` 是個 function designator,所以實際上會被轉為 pointer to function,也就是 `(*put)()`。
* 如果前面加上 `*` (dereference of) 成為 `*put()`,便會使 pointer to function returning type 變成 function returning type 的 function designator,然後又再被轉回 pointer to function,因此無論前面加多少 `*` 結果都會是一樣的。
* 如果前面加上 `&` (address of) 成為 `&put()` 的話,則會讓直接讓原本的 function designator 變為 pointer to function returning type。
* 因此無論在 `put()` 前加上 `*` 或 `&` ,最後都還是會轉為 pointer to function returning type。
## 第 2 週測驗題 測驗 `7`
:::info
推敲以下程式碼的作用:
```C
void hex2(unsigned int x) {
do {
char c = "0123456789abcdef" [x & 0xf];
printf("char %c for %d\n", c, x);
x >>= 4;
printf("char %c for %d\n", c, x);
} while (x);
}
```
:::
* 這段程式碼主要的作用是輸出一個整數的 16 進位表示法,在第三行的 `x & 0xf` 會取出 `x` 最低的四個 bits,並且剛好對應到字串 `"0123456789abcdef"` 中相對應的 16 進位字母,例如最低的四個 bits 假如是 `1011`,也就是十進位的 `11`,就會剛好取到字串中的 `"b"`。
* 每次迴圈當中,在取出 `x` 最低的四個 bits 對應的 16 進位字母後,就會將 `x` 往右 shift 四個 bits,直到 `x` 為 `0` 後跳出迴圈。
* 比較特別的是在每次迴圈當中,`x` 被 shift 的前後都會印出結果,也許是因為這邊的寫法是從 16 進位的低位印到高位,比較容易產生混淆,所以才將 shift 的前後的值都印出,方便觀察每一次迴圈的變化。
* 測試程式與實際執行結果如下:
```C=
#include <stdio.h>
void hex2(unsigned int x) {
do {
char c = "0123456789abcdef" [x & 0xf];
printf("char %c for %d\n", c, x);
x >>= 4;
printf("char %c for %d\n", c, x);
} while (x);
}
int main() {
hex2(100);
hex2(0xabc);
return 0;
}
```
```Shell=
$ ./quiz2-7
char 4 for 100
char 4 for 6
char 6 for 6
char 6 for 0
char c for 2748
char c for 171
char b for 171
char b for 10
char a for 10
char a for 0
```
:::success
延伸問題: 在 [glibc](https://www.gnu.org/software/libc/) 原始程式碼找出類似作用和寫法的程式碼,並探討其實作技巧
:::
* 以下為 [glibc/math/atest-sincos.c](https://code.woboq.org/userspace/glibc/math/atest-sincos.c.html) 中的一個函式,其中第 9 行雖然有多做了許多運算,但目的還是將特定位置的 4 個 bits shift 到最低位,並且與 `0xf` 做 `&` 運算,來從預先定義好的字串中取得對應的 16 進位數字。
```C=
static const char hexdig[] = "0123456789abcdef";
static void print_mpn_hex (const mp_limb_t *x, unsigned size)
{
char value[size + 1];
unsigned i;
const unsigned final = (size * 4 > SZ * mpbpl) ? SZ * mpbpl / 4 : size;
memset (value, '0', size);
for (i = 0; i < final ; i++)
value[size-1-i] = hexdig[x[i * 4 / mpbpl] >> (i * 4) % mpbpl & 0xf];
value[size] = '\0';
fputs (value, stdout);
}
```
## 第 3 週測驗題 測驗 `1`
:::info
考慮以下程式碼:
```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);
```
會發生什麼事?
:::
* 實際執行結果:
```shell=
$ gcc quiz3-1.c -o quiz3-1
quiz3-1.c: In function ‘main’:
quiz3-1.c:10:36: error: cannot take address of bit-field ‘x’
printf("Address of t.x is %p", &t.x);
^
```
* 查閱 C99 規格書可以得到以下的解釋:
> **§6.7.2.1 Structure and union specifiers**
>
> 8) A member of a structure or union may have any object type other than a variably modified type.^105)^ 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**;^106)^ its width is preceded by a colon.
> 10) 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. 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.
>
> 106) 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.
* 因此答案為 `(e)` 編譯失敗,不能將指標指向沒有對齊 1 byte 的結構體成員。
## 第 3 週測驗題 測驗 `4`
:::info
考慮以下程式碼:
```C
#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 = &((F1)->next);
newrec = malloc(sizeof(record));
newrec->value = F2(value);
newrec->next = F3;
F4 = newrec;
}
```
函式 insert_sorted 的作用是在 r 所指向已依據數值排序的 linked list 中,安插給定的字串,考慮到 string literal 的生命週期,應該在新建立的節點中,複製給定的字串。
請補完上述程式碼。
:::
:::success
延伸問題: 解釋運作原理,並新增刪除和搜尋節點的程式,附上完整的測試計畫
:::
* 以下為整個 quiz3-4.h 檔的內容,包含了填補完整的 `insert_sorted()` 函式,以及另外實做的 `delete_sorted()` 可以用來刪除節點,以及 `list_sorted()` 可以印出目前 list 中的內容。
```C=
#include <stdio.h>
#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;
}
void delete_sorted(record **r, const char *value)
{
while (*r && strcmp(value, (*r)->value) > 0)
r = &((*r)->next);
if (*r == NULL || strcmp(value, (*r)->value) != 0) {
printf("> \"%s\" not in list ", value);
return;
}
record *ptr = *r;
*r = (*r)->next;
free(ptr->value);
free(ptr);
}
void list_sorted(record **r)
{
printf("> ");
for(; *r; r = &((*r)->next))
printf("%s ", (*r)->value);
printf("\n");
}
```
* 在 `insert_sorted()` 中,`r` 是一個指標的指標,會在 while 迴圈中不斷指到每一個節點的 `next` 指標,並且去比較要插入的字串與 `next` 所指到的節點的 `value` 的大小,直到 `value` 不小於要插入的字串或是到了 list 的末端;這時便會配置一個節點的空間,並且使用 `strdup()` 配置一個字串的空間來將字串複製進去,最後則是將該節點的 `next` 指向現在 `r` 所指的指標指到的節點,並且讓 `r` 所指的指標指向新節點。
* 在 `delete_sorted()` 中,`r` 一樣會在 while 迴圈中不斷指到每一個節點的 `next` 指標,並且去比較要刪除的字串與 `next` 所指到的節點的 `value` 的大小,直到 `value` 不小於要插入的字串或是到了 list 的末端;這時如果 `value` 不等於我們要的字串或是我們已經到了 list 末端,就代表要刪除的字串不在 list 中;若是找到了我們要刪除的節點,需要先利用一個暫存的指標 `ptr` 指向我們要刪除的節點,接著讓 `r` 所指的指標指向下下一個節點,最後將 ptr 指到的節點中的 `value` 和節點的空間都釋放掉。
* 以下為完整的測試程式 quiz3-4.c:
```C=
#include "quiz3-4.h"
int main() {
record *ptr = NULL;
record **head = &ptr;
printf("Insert \"ba\" ");
insert_sorted(head, "ba");
list_sorted(head);
printf("Insert \"abc\" ");
insert_sorted(head, "abc");
list_sorted(head);
printf("Insert \"bb\" ");
insert_sorted(head, "bb");
list_sorted(head);
printf("Delete \"ba\" ");
delete_sorted(head, "ba");
list_sorted(head);
printf("Insert \"ac\" ");
insert_sorted(head, "ac");
list_sorted(head);
printf("Delete \"ba\" ");
delete_sorted(head, "ba");
list_sorted(head);
printf("Insert \"c\" ");
insert_sorted(head, "c");
list_sorted(head);
printf("Delete \"abc\" ");
delete_sorted(head, "abc");
list_sorted(head);
}
```
* 測試結果:
```shell=
$ gcc quiz3-4.c -o quiz3-4
$ ./quiz3-4
Insert "ba" > ba
Insert "abc" > abc ba
Insert "bb" > abc ba bb
Delete "ba" > abc bb
Insert "ac" > abc ac bb
Delete "ba" > "ba" not in list > abc ac bb
Insert "c" > abc ac bb c
Delete "abc" > ac bb c
```