# 2023q1 Homework1 (lab0)
contributed by < `Lomomo` >
## 前言
:::spoiler
寫這段主要是來提醒和勉勵自己。
大學畢業到現在已經工作兩年,雖然拿到看似還過得去的薪水,但是因為自己的專業不足,對於每天自己產出爛程式碼的生活感到反感,於是來參加課程。
第一周的上課老師說很多所謂的得過且過、能動就好的那種半吊子工程師根本就是在說我,在 Maslow’s pyramid of code review 中,我的確連邊都摸不到。
**誠實面對自己,面對自己的不足**。
> [name=Vic Luo][time=Feb 17, 2023]
:::
<!-- ## 教材閱讀 -->
<!-- [GNU Make Manual Note](https://hackmd.io/@Lomomo/GNU_Make_Manual_Note) -->
<!-- ### 你所不知道的 C 語言: 開發工具和規格標準 -->
## 開發環境
```shell
$ gcc --version
gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 43 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 16
On-line CPU(s) list: 0-15
Vendor ID: AuthenticAMD
Model name: AMD Ryzen 7 3700X 8-Core Processor
CPU family: 23
Model: 113
Thread(s) per core: 2
Core(s) per socket: 8
Socket(s): 1
Stepping: 0
Frequency boost: disabled
CPU max MHz: 5040.9170
CPU min MHz: 2200.0000
BogoMIPS: 8200.08
Virtualization features:
Virtualization: AMD-V
Caches (sum of all):
L1d: 256 KiB (8 instances)
L1i: 256 KiB (8 instances)
L2: 4 MiB (8 instances)
L3: 32 MiB (2 instances)
NUMA:
NUMA node(s): 1
NUMA node0 CPU(s): 0-15
```
## 開發過程
### 開發環境設定
`cppcheck` install from gitgub.
```shell
git clone https://github.com/danmar/cppcheck
sudo apt install libpcre3-dev
make install -j24 MATCHCOMPILER=yes FILESDIR=/usr/share/cppcheck HAVE_RULES=yes CXXFLAGS="-O2 -DNDEBUG -Wall -Wno-sign-compare -Wno-unused-function"
$ cppcheck --version
Cppcheck 2.11 dev
```
### q_insert_head
`head` 若為 `Null` 回傳 `false`.
要為 `element_t` 和 `element_t->value` 配置記憶體.
`strlen()` 回傳字串長度不包含 `\0`.
`strncpy()` 不會幫字串加入結尾字元, 必須另外處裡.
### q_remove_head
若 `head->next` 指向自己,表示 queue 為空.
`list_del` 在 `list.h` 中說明
> the node has to be handled like an uninitialized node.
使用 `list_del_init` 移除並初始化節點, 確保移除後節點內指標是安全的.
:question: 做到這邊發現我不知道怎麼取得 `element_t->value`.到 `qtest.c` 中觀察到 `q_show()` 有使用到 `list_entry` ,在這裡面還有 `container_of` 在教材中有搜尋到回去詳讀.
```c
#define container_of(ptr, type, member) \
((type *) ((char *) (ptr) -offsetof(type, member)))
```
參考 [Linux 核心原始程式碼巨集: container_of](https://hackmd.io/@sysprog/linux-macro-containerof "title")
`offsetof` 會回傳成員的位址減去 struct 的起始位址
> `offsetof` 會處理 struct 記憶體對齊的部份
:::spoiler `offsetof` example code
```c
#include <stddef.h>
#include <stdio.h>
struct data {
short a;
char b;
double c;
};
int main() {
struct data x = {.a = 25, .b = 'A', .c = 12.45};
char *p = (char *) &x;
printf("a=%d\n", *((short *) p));
p += sizeof(short);
printf("b=%c\n", *((char *) p));
p = (char *) &x + offsetof(struct data, c);
// p += sizeof(char);
printf("c=%lf\n", *((double *) p));
printf("p=%p, &x.c=%p\n", p, &(x.c));
printf("offsetof(struct data, c): %ld\n", offsetof(struct data, c));
}
```
```bash
$ ./main
a=25
b=A
c=12.450000
p=0x7ffe438c9c88, &x.c=0x7ffe438c9c88
offsetof(struct data, c): 8
```
下面是結果是直接對 struct member type 計算偏移的結果, 會有 padding 導致 c 得出的結果有誤.
```bash
./main
a=25
b=A
c=190346578428946467411813376969926036231236201599375216348857262157744255007426608362121915988857235308148337053537869186724122510073570901116281414778212356702476212757602596560790618112.000000
p=0x7ffc47e4ae73, &x.c=0x7ffc47e4ae78
```
:::
:bulb: 了解 `offsetof` 後, 回到作業中觀察, 就可以知道麼去獲取 `element_t` 的位置了.
展開 Macro 後如下
```c
// #define container_of(ptr, type, member)
container_of(head->next, element_t, list);
((element_t *) ((char *) (head->next) - offsetof(element_t, list)))
```
* `(char *) (head->next)` 在 element_t 中 list_head 的位址
* `offsetof(element_t, list)` element_t->list 的位址減去 struct 的起始位址
* 上述兩個位址相減就得到最外層 element_t 的位址
* 最後 `(element_t *)` 回傳
### q_reverse
利用 `list_for_each_safe` 的特性就可以順利的走訪 queue 及 `node->next` 和 `node->prev` 交換操作.
最後 `node` 會指回 `head` 即可完成 `head->next` 和 `head->prev` 交換的操作.
### q_delete_mid
剛開始就很直觀的直接去實做出來, 先計算 queue size 的一半的是多少, 然後走訪 queue 得到 middle 的位置.
在教材中有看到可以利用快慢指標的方式去取得之後再來改寫.
另外在 `qtest` 測試 `dm` 的過程中,有發現計算 queue 大小的變數 `current->size` 除了程式報錯的情況都會去執行 `current->size--` 的運算, 導致有負數的出現. 已經提交 Pull request 等待 code reivew.
下面為 gdb 除錯的紀錄
```
0x0000555555557dd8 in do_dm (argc=<optimized out>, argv=<optimized out>) at qtest.c:689
689 current->size--;
(gdb) c
Continuing.
Hardware access (read/write) watchpoint 1: current->size
Old value = 0
New value = -1
Hardware access (read/write) watchpoint 1: current->size
Value = -1
do_dm (argc=<optimized out>, argv=<optimized out>) at qtest.c:690
690 q_show(3);
```
使用快慢指針改進原本用計數取得中間節點的時間複雜度, 少了計算 queue 的大小, 使得時間複雜度從 $O(n^2)$ 變成 $O(n)$.
### q_swap()
初步思考如下圖,A 節點透過 `list_del`移出後, B 節點對於 C 節點後面的 list 即為 head node. 在透過 `list_add(A, B)` 把 A 節點加到 C list 的開頭, 這樣對於 A, B 兩節點即完成交換的動作. `list_del` 和 `list_add` 也已經在 list.h 封裝成 `list_move`.
在 `list_for_each` node 在 swap 完成後, 也就剛好指向下一對節點.
最後走訪所有節點, 執行上述步驟即完成 q_swap().

### q_sort()
嘗試去實做時, 因為 merge sort 需要取得中間節點, 就使用快慢指針回去改寫 get_middle_node 的方法.
閱讀教材的過程, 真的覺得自己理解指標變數的速度很慢, 特別要去理解間接指標的時候更覺得有挫折感, 下面是大概描述我看到這段程式腦海理解他的聲音,雖然感覺描述這段很沒有意義,但是就是有個結在卡在那的感覺,如果有更好的理解在麻煩多指教,不過追根究底我覺得還是指標的使用太少了,還得多多練習.
```c
// head 是指向結構體的變數, 存有結構體的位址
struct ListNode *head = malloc(stuct ListNode);
// ptr 是 pointer to pointer to struct ListNode 知道他是這樣但是感覺有點迷糊
struct ListNode **ptr = &head;
// ptr equal &head
// *ptr equal head
// **ptr equal *head equal struct Listnode
ptr = &(*ptr)->next
// ptr = &(head)->next
// ptr = &(*head).next
```