# 2025q1 Homework1 (lab0)
contributed by < horseface1110 >
{%hackmd NrmQUGbRQWemgwPfhzXj6g %}
## 開發環境
```
$ gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 40 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 1
On-line CPU(s) list: 0
Vendor ID: GenuineIntel
Model name: QEMU Virtual CPU version 2.5+
CPU family: 15
Model: 107
Thread(s) per core: 1
Core(s) per socket: 1
Socket(s): 1
Stepping: 1
BogoMIPS: 5199.99
Flags: fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscall nx lm constant_tsc nopl xtopo
logy cpuid tsc_known_freq pni ssse3 cx16 sse4_1 sse4_2 x2apic popcnt aes hypervisor lahf_lm cpuid_fault pti
Virtualization features:
Hypervisor vendor: KVM
Virtualization type: full
Caches (sum of all):
L1d: 32 KiB (1 instance)
L1i: 32 KiB (1 instance)
L2: 4 MiB (1 instance)
L3: 16 MiB (1 instance)
NUMA:
NUMA node(s): 1
NUMA node0 CPU(s): 0
Vulnerabilities:
Itlb multihit: KVM: Mitigation: VMX unsupported
L1tf: Mitigation; PTE Inversion
Mds: Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown
Meltdown: Mitigation; PTI
Spec store bypass: Vulnerable
Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization
Spectre v2: Mitigation; Retpolines, STIBP disabled, RSB filling
Srbds: Not affected
Tsx async abort: Not affected
```
## 針對佇列操作的程式碼實作
### q_free
commit:[76f27e2](https://github.com/sysprog21/lab0-c/commit/76f27e2834f589f6c46d67be7e3c2fa6a53e6b9d)
```
/* Free all storage used by queue */
void q_free(struct list_head *head) {
element_t *entry, *safe;
list_for_each_entry_safe(entry, safe, head, list) {
if (entry->value) {
free(entry->value);
}
free(entry);
}
free(head);
}
```
發現list.h有一個function:`q_release_element`,之前忘記檢查head是否為空,新增
```
if (!head || list_empty(head)) {
free(head);
return;
}
```
### q_insert_head
commit:[9359749](https://github.com/sysprog21/lab0-c/commit/93597492d1b9f8639a0091b3f011c70766224e54)
一開始的用list_empty()檢查是否為空,新分配空間後,再檢查是否有成功分配,最後用`list_add`添加節點在頭部。
但發現無法正確執行插入,我想是沒有檢查是否成功放入值到新的節點
所以新增了檢查的函式。
```
if (!new->value) {
free(new);
return false;
}
```
### q_remove_head
commit:[f8f5cf2](https://github.com/sysprog21/lab0-c/commit/f8f5cf25ba8741465e907e812a2bb83cd3bb4e7b)
- 實作細節:
移除並返回鏈結串列的第一個節點(head->next)。
若提供了 sp,則將該節點的 value 字串複製到 sp 中。
- 問題:一開始看不懂sp要做什麼
### q_insert_tail、q_remove_tail
commit:[c2cd219](https://github.com/sysprog21/lab0-c/commit/c2cd219d940afd8ab9cddc914ab43fd131676b20)
inser_tail跟remove_tail是一起做的
- 功能描述:
- q_remove_head() 與 q_remove_tail() 分別負責從雙向循環鏈結串列的頭部與尾部移除元素。若有提供 sp 緩衝區,則會將被移除節點的字串值安全地複製至 sp。
- 實作細節:
- 空串列檢查:在移除前,會先檢查鏈結串列是否為空,避免對空串列進行操作。
- 字串值複製:若提供了 sp 且節點的值不為空,會使用安全的字串複製方式,確保不會超出緩衝區大小,並保證字串結尾為 '\0'。
- 移除節點:透過 list_del() 將目標節點從鏈結串列中移除,並確保鏈結結構的正確性。
- 節點返回:函式會返回已移除的節點指標,供外部進行記憶體釋放或後續處理。
### q_delete_mid
Commit:[6f51bb3](https://github.com/sysprog21/lab0-c/commit/6f51bb31b5a9e45a41f8281bde408805e51a1983)
- 實作細節:
- 功能:刪除雙向循環鏈結串列中的中間節點,成功返回 true,否則返回 false。
- 檢查 head 是否為空或空串列。
- 使用快慢指標找到中間節點。
- 移除節點並釋放 value 和節點記憶體。
邏輯重點:偶數個節點的話,是刪除正中間的下一個,一開始寫錯,會抓到前一個
### q_sort
commit:[f2e792f](https://github.com/sysprog21/lab0-c/commit/f2e792f1c98351971ed9fdbf005a8021bc9564ec)
- 會用到q_size......
- 實作細節:
這段程式碼實作了雙向循環鏈結串列的歸併排序,主要包含以下函式:
- compare()
比較兩個節點的值,根據 descend 判斷排序方向(遞減或遞增)。
- merge()
合併兩個已排序的鏈結串列,並維持 prev 和 next 的正確連接,最後恢復循環結構。
- q_merge_two_lists()
使用遞迴將鏈結串列分割為左右兩部分,分別排序後再合併。
- q_sort()
主排序函式,檢查特殊情況、暫時斷開循環、執行遞迴排序,最後恢復循環鏈結。
重點:確保每次分割與合併後,鏈結串列的 prev 和 next 指標正確,避免結構損壞或無窮遞迴。
- 遇到的問題:
- 起初的compare function沒有固定好回傳ture / false分別代表什麼,後來大改。
- q_merge_two_lists()一開始沒有做好分割串列,導致分割後的串列仍舊相連,在執行時出現無窮迴圈
- merge():邏輯錯誤,導致出現segmentation fault,最後使用pointer to pointer編寫。
- 最大的問題!!即使沒有呼叫q_size(),但還是會用到,我q_size()沒有先寫,
#### merge
```
struct list_head *merge(struct list_head *left, struct list_head *right, bool descend){
left->prev->next = NULL;
right->prev->next = NULL;
struct list_head *head = NULL, *ptr = head, *current = head;
/*head 是 dummy node,ptr是目前被移出的節點,current是新串列的尾巴*/
while(left && right){
if(compare(left, right, descend)){
ptr = left;
left = left->next;
list_del_init(ptr);
current->next = ptr;
ptr->prev = current;
current = current->next;
}else{
ptr = right;
right = right->next;
list_del_init(ptr);
current->next = ptr;
ptr->prev = current;
current = current->next;
}
}
if(left){
current->next = left;
}else{
current->next = right;
}
return head;
}
```
segmetation fault
### q_ascend、q_descend
Commit:[8feb2a4](https://github.com/sysprog21/lab0-c/commit/8feb2a418283fbb7128aa070ec3ad31900b83525)
- 實作細節:
- 此函式從右往左遍歷雙向循環鏈結串列,刪除右側存在更大值的節點。先檢查串列是否為空或僅有一個節點,若是則直接返回。遍歷時,從倒數第二個節點開始,max 初始化為最後一個節點。若當前節點小於 max,則刪除並釋放記憶體;否則更新 max。遍歷至 head 為止,最後返回 0 表示完成。
- 困難:
- 一開始用`current = head`並在迴圈中`current = current->prev`,不直觀,改為`head = head->prev->prev`
- 原本手刻element_t釋放節點,後來發現`q_release_element`
- 直接從倒數第二個開始檢查,若大於等於max,保留並更新max;小於max,則刪除
但會遇到
```
l = [c a d c b a]
cmd> descend
l = [ ... ]
ERROR: Queue has more than 0 elements
Freeing queue
```
更改為`return q_size(head)`即可
### q_reverse_segment
- 目標:反轉一個區間內的節點順序
- 排序:
- 先使用 q_sort() 對鏈結串列進行排序,確保相同元素相鄰,便於後續判斷重複。
- 歷與判斷:使用遍歷邏輯依序比對相鄰節點的值:
- 發現重複時,刪除該節點,並標記當前為重複狀態。
- 遇到重複結尾時,將標記為重複的起始節點刪除,確保所有重複元素被移除。
- 記憶體釋放:每次刪除節點時,確保釋放其佔用的記憶體,避免洩漏。
- 困難:因為輸入的第一個節點也是需要被反轉的,所以不能用以前的head->next邏輯當第一個節點。在這邊錯了蠻多次的
### q_reverse
直接使用q_reverseK
### q_reverseK
Commit:[853e3cd](https://github.com/sysprog21/lab0-c/commit/853e3cd174d2082e2980d5ccd6a7e7fe8e55d7c0#diff-3f6f16518a74b523b2cad7ede4d4a9c11605317dfed2905cdaf12e242ee05923L143-L167)
```diff=
- struct list_head *start = NULL, *end = NULL, *current = NULL;
+ struct list_head *start = head, *end = NULL, *current = start;
```
遇到segmentation fault
發現:index = 0 要放在 index == k 時
current會指向現在檢查到的最後一個,但 q_reverse_segment後,他指向的node不變,導致錯誤
### q_merge
q_merge一次合併兩個queue,直到全部都合併完,使用了`merge_lists_with_sentinel_node`,前半部分跟merge很像,但不用斷頭,直接用L1的頭當回傳的頭,最後把L2的頭初始化
##3 q_descend
直接從倒數第二個開始檢查,若大於等於max,保留並更新max;小於max,則刪除
但會遇到
```
l = [c a d c b a]
cmd> descend
l = [ ... ]
ERROR: Queue has more than 0 elements
Freeing queue
```
更改為`return q_size(head)`即可
### q_delete_dup
Commit:[853e3cd](https://github.com/sysprog21/lab0-c/commit/853e3cd174d2082e2980d5ccd6a7e7fe8e55d7c0)
- 實作細節:
- 排序:先使用 q_sort() 對鏈結串列進行排序,確保相同元素相鄰,便於後續判斷重複。
- 遍歷與判斷:使用遍歷邏輯依序比對相鄰節點的值:
- 發現重複時,刪除該節點,並標記當前為重複狀態。
- 遇到重複結尾時,將標記為重複的起始節點刪除,確保所有重複元素被移除。
- 記憶體釋放:每次刪除節點時,確保釋放其佔用的記憶體,避免洩漏。
- 錯誤:
```
queue.c:152:43: error: passing argument 1 of ‘strcmp’ makes pointer from integer without a cast [-Werror=int-conversion]
152 | if(*entry->value != head & strcmp(*entry->value, *safe->value) == 0){
| ^~~~~~~~~~~~~
| |
| char
```
Strcmp的參數為指標
錯誤:
```
queue.c:152:26: error: comparison between pointer and integer [-Werror]
152 | if(*entry->value != head & strcmp(*entry->value, *safe->value) == 0){
```
*entry->value 改為 entry->list.next
錯誤:
```
queue.c:161:22: error: passing argument 1 of ‘list_del’ from incompatible pointer type [-Werror=incompatible-pointer-types]
161 | list_del(entry);
| ^~~~~
| |
| element_t *
```
list_del(entry->list.next->prev);,目標:移除當下的node
最後:需要先sort才能使用
錯誤:
```
l = [b r b r b r a]
cmd>
cmd> dedup
ERROR: Duplicate strings are in queue or distinct strings are not in queue
l = [a]
Freeing queue
```
### swap
```
/* Swap every two adjacent nodes */
void q_swap(struct list_head *head)
{
// https://leetcode.com/problems/swap-nodes-in-pairs/
q_reverseK(head, 2);
}
```
reverseK修好就好了
## 閱讀論文〈Dude, is my code constant time〉
### lab0 dudect程式碼的缺陷
Commit [156c254](https://github.com/horseface1110/lab0-c/commit/156c254b3d489bcc858a3b2f12d2801522826d41)
rebase後,commit 遇到問題,說是printf出壞東西
```
oloomb@oloomb:~/linux2025/tmp/tmp/lab0-c$ git commit -a
--- modified dudect/fixture.c
+++ expected coding style
@@ -75,7 +75,7 @@ static void update_statistics(const int6
/* do a t-test on the execution time */
t_push(t, difference, classes[i]);
}
-}
+}
static bool report(void)
{
[!] dudect/fixture.c does not follow the consistent coding style.
Make sure you indent as the following:
clang-format -i dudect/fixture.c
Following files were changed:
- dudect/fixture.c : 1 insertions(+), 1 deletions(-)
Running fmtscan...
No dictionary found.
[!] Check format strings for spelling
```
執行`sudo apt install wamerican`後問題解決,是他的字典
原本的diot中沒有先進行排序,就直接進入update_statistics,導致依據這些閾值裁剪的操作失準。這會使得統計上下文中 t 值、t_cropped 的數據累計出錯,進而導致最終的 t-test 結果不可靠,也就是說可能錯誤判定目標函式是否具有常數時間行為。所以我在這邊加了一個排序`prepare_percentiles(exec_times);`,但出現`segmentation fault `,我猜想問題是在使用 t_cropped 或 t_second_order 前,沒有分配及初始化它們導致的。
``` diff
static void init_once(void)
{
init_dut();
t_init(t);
+ for (size_t i = 0; i < DUDECT_NUMBER_PERCENTILES; i++) {
+ t_cropped[i] = malloc(sizeof(t_context_t));
+ if (!t_cropped[i]) die();
+ t_init(t_cropped[i]);
+ }
+ t_second_order = malloc(sizeof(t_context_t));
+ if (!t_second_order) die();
+ t_init(t_second_order);
}
```
Commit [db4bd7b](https://github.com/horseface1110/lab0-c/commit/db4bd7b4a1bf5592adfe5af3404eb5adb394183a)
雖然這樣可以通過測驗,但我心裡不踏實,因為只對`exec_time`排序,讓`exec_time`和`classes`脫鉤了。所以我改了`prepare_percentiles`,用結構保存 exec_times 與 classes 的對偶型別
```
typedef struct {
int64_t exec_time;
uint8_t cls;
} pair_t;
```
再把有效區間複製資料到 pairs 陣列,排序完後再更新原始陣列