contributed by < Booker-Chen
>
brian049
q_ascend
, q_reverseK
, q_reverse
)Remove still occur nullPointer problem while using cppcheck.
,若是不指明 Remove
這個字詞是 q_remove_head
或是 q_remove_tail
函式的名稱,會影響到描述內容的本意。打開 lab0 的 Hackmd 就直接一股腦兒地開始瞎寫,根本沒看到測試工具跟作業要求那邊,花了一堆時間在通靈,然後又超晚才交表單跟寫 Hackmd 。
commit 那邊也一堆問題真的是。
留意各式細節,唯有重視小處並步步為營,方可挑戰原始程式碼規模超越三千七百萬行的 Linux 核心。
cppcheck --enable=all --suppress=missingIncludeSystem <c file>
:用來檢測 <c file>
的靜態程式碼有沒有出錯。
make check
:用來看函式有沒有正確執行。
make test
:測試程式。
clang-format -i queue.c
:commit
之前可以用這個指令檢查程式碼排版。
q_new
allocate 的翻譯是「配置」,以區隔 dispatch (分配/分發) 的翻譯。
用 malloc
分配 記憶體空間,如果 malloc
失敗的話就回傳 NULL
。
malloc
成功後用 INIT_LIST_HEAD
建立一個空的佇列。
避免非必要的項目縮排 (即 *
和 -
),以清晰、明確,且流暢的漢語書寫。
一開始的程式碼為:
結果在 commit
的時候跳出一段關於 [variableScope]
的問題出來
然後我用 cppcheck --enable=all queue.c
檢查我的程式碼又找到一個 [nullPointer]
的問題
結合 list.h
中的 list_for_each_entry_safe()
和 queue.h
中的q_release_element()
做了修改之後
程式碼(修改版本一):
HackMD 不是讓你張貼完整程式碼的地方,GitHub 才是!因此你在開發紀錄只該列出關鍵程式碼 (善用 diff 標示),可附上對應 GitHub commit 的超連結,列出程式碼是為了「檢討」和「便於他人參與討論」,不是用來「假裝自己有付出」
這樣就解決了。
不知道為啥一開始那樣寫會有 [valuableScope]
的問題。
翻閱 Cppcheck 的手冊。
malloc()
完成動態分配記憶體和賦值,中間包含對分配失敗的處理。memcpy()
將字串複製進分配的記憶體,並且在結尾補上 '\0'
。list_add()
插入佇列的首端。和 q_insert_head
雷同。
q_insert_head
和 q_insert_tail
不知道為甚麼會在這個奇怪的地方有 Memory Leak
透過錯誤訊息發現出錯的地方應該就在 list_add
這裡,後來去作業區觀摩一下大家 insert
的部分,發現大家的寫法都是 $element->list
。
於是我就把括號刪掉
再執行一次 cppcheck queue.c
,發現 Memeory Leak
的問題就被解決了。
問題:
&(element->list)
和&element->list
是等價的嗎 ?
list_del
將其從佇列中移除。strncpy
複製進 sp
裡面,進行複製前先檢查 sp
是否存在以及 buffsize
是否大於 0。和 q_remove_head
雷同。
q_remove_head
和 q_remove_tail
又發生了跟 q_free
一樣的 [nullPointer]
問題。
透過 list_for_each
遍歷 整個鏈結串列並且用 len
來記錄鏈結串列的長度。
list_del
將該節點從佇列中移除。q_release_element
將其刪除。根據 Dictionary.com 的解釋: (作為及物動詞和不及物動詞都有類似的意思,以下列出作為及物動詞的寓意)
其實這意思很好懂,就像我們「走過」/「穿越」校園一般,於是 traverse a linked list 就會是「(用某種手段) 存取多個鏈結串列的節點」,但這裡卻沒有必要「所有」的範圍:英語的 "move over/through" 用於某個區域時,根本沒有這樣的隱含意義。如果將 traverse 翻譯為「遍歷」,就會導致「超譯」,也就是跳脫「直譯」和「意譯」。
當我們回頭看 "traverse" 所在的技術描述內容,例如 "traverse every node",若翻譯為「遍歷每個節點」,那麼既然都「遍」(意即「全面」、「到處」),又何來「每個」節點呢?於是,合理的翻譯應改為「逐一走訪每個節點」 —— 差異在哪?在 "traverse every node" 的應用場景中,可能是我們嘗試在鏈結串列尋找特定的節點內含的資料,一旦找到就停止,或者我們要偵測給定的鏈結串列是否包含環狀 (circular) 結構 ,並沒有真的要「遍」(到處/全面)「歷」(意即「經過」) 每個節點。在我們的用語中,要區分「意圖」(intention) 和「實際作用」(reaction),濫用「遍歷」會使得語意不清,從而難以推測英語原文的訴求。
還有個更重要的原因是,「遍歷」這詞已在理工領域存在,且廣泛使用,即「遍歷理論」(Ergodic theory),後者是研究具有不變測度 (invariant measure) 的動力系統及其相關問題的一個數學分支。 遍歷理論研究遍歷變換,由試圖證明統計物理中的遍歷假設 (Ergodic hypothesis) 演進而來。
在統計學中,若單一個物理系統在不同時間內重複相同的實驗 (如丟擲一枚硬幣),其取樣數據所得的統計結果 (如硬幣出現正面的機率) 和極多個完全相同的物理系統的系集 (如丟極多個相同的硬幣) 在同時作相同的實驗取樣數據的統計結果假設為相同時,此種假設即稱為「遍歷性假設」或「遍歷假設」。基於這個假設,對時間平均的統計方式及結果,便可由對系集的平均及統計方式得到。在一般物理系統中,尤其在統計力學範圖中,均採用此遍歷性假設為基本的假設。在流體力學中對亂流的實驗分析,亦是採用此假設。
遍歷 (Ergodic) 源於以下二個希臘詞:
最初這是由奧地利物理學家波茲曼 (Ludwig Boltzmann) 於統計力學領域 (statistical mechanics) 提出的概念,其一廣為人知的結果是:在經過長時間後,時間平均將會趨近空間平均。此事實在動力系統扮演極重要的角色,在隨機分析領域亦然。
因此,若貿然將 traverse 翻譯為「遍歷」,一來語意不清,二來增加和理工多項領域專業人員的溝通成本,實在得不償失。
list_for_each_entry_safe
find_dup
用來比對相鄰的兩個節點的字串,如果相同即為 true
。del_dup
用來確保刪除掉所有重複的節點。這樣寫會有 [variableScope]
的問題
前面的 q_free
也有出現過但是莫名其妙的就解學了。這次看不出個所以然,上網找資料看到 CppCheck. The scope of the variable can be reduced (and loop)跟我遇到的問題很像,照著嘗試一下就解決了。
為何不查閱 Cppcheck 的手冊?捨近求遠。
解決方法是說這個變數如果只會在迴圈裏面用到的話那就宣告在迴圈裏面比較好。但是問題來了,為甚麼會比較好?
先判斷佇列是否存在、是否為空、是否只有一個節點。
設變數 prev
和 curr
分別代表相鄰兩節點,透過 prev->next = curr->next
、curr->next->prev = prev
等的操作交換相鄰節點的位置。
透過迴圈判斷 prev
和 curr
其中一人是否為 head
,是的話代表已經遍歷 整個鏈結串列,迴 圈中止。
善用 List API,撰寫出更精簡的程式碼。
HackMD 不是讓你張貼完整程式碼的地方,GitHub 才是!因此你在開發紀錄只該列出關鍵程式碼 (善用 diff 標示),可附上對應 GitHub commit 的超連結,列出程式碼是為了「檢討」和「便於他人參與討論」,不是用來「假裝自己有付出」
find_greater
用來檢查右邊是否有比自己小的節點 (自己是否比右邊的節點大)。find_greater
和迴圈搭配,重複檢查直到整個佇列都是嚴格遞增的。list_for_each_entry_safe
遍歷整個佇列。entry
和 safe
倆倆比較,當 entry
的字串比 safe
大的時候,就把 find_greater
設為 true
並且移除 entry
,然後跳出迴圈再重新遍歷。和 q_ascend
雷同,只是改成嚴格遞減。
在執行 trace-06-ops
時,發現有 Segmentation fault
的問題。
用 make valgrind
檢查後發現這個錯誤訊息
回去檢查之後發現是當 element2
指向的位置為 head
時,仍會與 element1
做 strcmp
。但是 head
只是一個 list_head
建構子,不是 element_t
所以才會跳上面這個錯誤訊息。
只要在 list_for_each_entry_safe
裡面加上這行就解決了。
但是又跳出 Memory Leak
的問題。
清楚標註你參考的同學的 GitHub 帳號名稱。
逐一檢查 trace-06-ops
會用到的函式以及觀摩同學們 這些函式的寫法後,發現在 descend
中移除的節點也要把其空間透過 q_release_element
釋放掉,但是在題目敘述中卻是寫 remove
。
阿這邊其實教授有講,自己沒認真聽,烙賽了。
這樣 trace-06-ops
就過了。
next
和 prev
對調。q_new
創建兩個新佇列的 head
,分別是 reverse_op
和 reverse_concat
。list_move
一直移到 reverse_op
的首端 (這樣就等於反轉),當迴圈跑了 k
次之後,就把 reverse_op
透過 list_splice_tail_init
接到 reverse_concat
上。k
之後,再把 reverse_concat
透過 list_sprice_init
接到原始佇列的首端。reverse_op
和 reverse_concat
釋放掉。寫完之後測試發現…這個函式好像也不能動態分配記憶體。
所以就只能換一種寫法了
k
個段落做一次反轉。reverse_seg
來達成每 k
個段落反轉一次,中間會用到前面寫好的 reverse
。不用 malloc
的 reverseK
:
採用遞迴式的 merge sort
程式碼及架構參考 ShallowFeather。
透過快慢指標找到中心點。
建立兩個佇列的 head
,分別是 left
和 right
。透過 list_splice_tail
和 list_cut_position
將原始佇列切分成左右兩塊。
透過 mergeTwo
將被切分的段落比較並且合併。
你如何確保目前的測試程式已涵蓋排序演算法最差的狀況?
q_merge
是將所有已排序好的佇列合併成一個同樣排序好的佇列。
因此我們會用到 queue_contex_t
這個結構體。
因為 chain
連接著所有的佇列,所以我們可以用 list_for_each_entry
遍歷整個 chain
,將除了第一個佇列以外的佇列都與第一個佇列合併並且計算長度。
之後再對佇列使用先前寫好的 q_sort
排序即可。
寫到目前這個階段在本地執行 make test
可以拿滿 100 分。但是推到 GitHub 上透過 Action 測試的時候發現 trace-17-complexity
沒過。
在密碼學的實作上,如果程式碼的運行不是常數時間,就能夠透過監測程式碼的執行時間進行時序攻擊,從而取得密碼或金鑰等的機密資訊。Paul C. Kocher 在 1996 年發表的這篇論文 Timing Attacks on Implementations of Diffie-Hellman, RSA, DSS, and Other Systems 就是透過監測執行時間進而取得私鑰破解加密系統。
網路上有很多其他常數時間的檢測工具,像是 ctgrind 和 ctverif,那為什麼我們要使用 dudect 呢 ? 因為前面提到的這些檢測工具需要模擬硬體設備,然而要模擬出一個完整並且正確的硬體設備並不是一件容易的事。反觀 dudect 則是透過 leakage detection tests,在目標平台上對一段程式碼做執行時間的統計學分析,如此一來就巧妙的規避需要模擬硬體的問題。
dudect 定義了一些 struct
來協助他完成 timing leakage detection。
我們拿 dudect 給我們的 example.c 來做測試。在 example.c 中定義了 SECRET_LEN_BYTES
這個 macro
。我們的目標是測試 check_tag
這個函式的運行是否為常數時間,do_one_computation
這個函式則會被一直執行。