contributed by <0xff07>
uname -i
:
lscpu
:
cat /etc/lsb-release
:
queue_t
因為要求尾端插入與長度查詢的複雜度是 ,因此在資料結構中加入 tail
與 size
分別紀錄尾端與長度:
q_free
若 head
為空,則顯然記憶體已經釋放。若 head
非空,則「釋放 head
」可遞迴地視為「釋放串列第一個元素(迴圈中暫存於 tmp
) ,並釋放剩下所有元素形成的串列(q -> head -> next
)」 。
而再釋放串列中的元素之後,再釋放 q
即可。
若在 free
中傳入空指標,在測試程式中將出現 Attempt to free NULL
的錯誤訊息,並且扣分(儘管 man
中寫到傳入空指標是合法的)。因此這邊所有的 free
都事先檢測是否為空指標。
q_insert_head
使用 do{...} while(0)
配合 break
,達到 goto
的效果,並進行各種例外處理。並在插入後,將屁股尾端維護好。正在思考例外處理與判斷的邏輯是否能夠更精減(立刻回去翻上週作業嗚嗚)。
另外發現若將 14. 15 行改成 cpumpound-literal 的寫法:
在 git commit
時似乎會被檢查出 memory leak,跑出:
但該作法可以順利通過 qtest
。因此不是很確定這種用法是否將造成 memory leak,或是有什麼潛在的不良影響。相關資料待查。
q_insert_tail
大致上與前一個函數相同,紀錄尾端並在插入後維護好 tail
與 head
的指標。「新增 list_ele_t
」似乎可以寫成一個函式,但發現使用 strdup
時,即使 man
中提到:
The strdup() function returns a pointer to a new string which is a duplicate of the string s. Memory for the new string is obtained with malloc(3), and can be freed with free(3).
即 strdup
中的記憶體由 malloc
配置,但使用 strdup
時測試程式,仍會判定沒有正確配置記憶體(見「其他」),因此尚未測試。
q_remove_head
依照要求,除了釋放記憶體,必要時得將節點中存的字串進行複製。因此照題目要求寫出來:
q_size
直接回傳存好的值大小:
q_reverse
假定 ,則:
若串列為空,或是串列僅有一個元素。則自己就是自己的反轉
若已經有前 個元素反轉的串列 :
則將第 個元素加入反轉串列的頭部:
就能構造出前 個元素反轉的串列。以此遞推下去。
前 2 個 if
敘述描述的是第 1. 中的狀況; 而其後的程式用於處理 2. 中的狀況。
我覺得這樣的實作看起來並不一目瞭然。似乎有待改進。
在 harness.[ch]
當中,可以看到記憶體管理相關的內容。該記憶體管理機制相關說明如下:
將原先的 malloc
與 free
使用 test_malloc
與 test_free
取代。該部份可見 harness.h
最後面:
所以實際上,在 queue.c
中呼叫 malloc
與 free
時,其實式分別呼叫 test_malloc
與 test_free
兩個函式。以下都用 test_malloc
與 test_free
稱呼該二函式(而不是 malloc
與 free
)。
所有因呼叫 test_malloc
得來的記憶體,實際上是紀錄在一個名為 allocated
的一個 doubly-linked list 中。該 linked list 結點的結構為 block_ele_t
,定義在 harness.c
中:
在這個結構體中,最後一個成員 payload[0]
是實際給呼叫者使用的記憶體開頭。該成員使用到的 GCC 中 "Array of Length Zero" 。該功能簡便處在於:若結構體中的最後一個成員是大小為 0 的陣列,那麼可以透過 malloc(sizeof(block_ele_t) + 希望 payload 後面接著多少記憶體)
來達成「最後一個成員大小可以任意指定」的效果(另外一個好處是 malloc
跟 free
一次就可以完成,不用替 payload
開頭的記憶體再 malloc
一次)。malloc_test
中可以發現以下程式:
這個技巧表現在程式的 malloc(size + sizeof(block_ele_t) + sizeof(size_t))
當中。但除了 sizeof(block_ele_t)
與 size
之外, 還多了一個 sizeof(size_t)
的大小。這是因為使用者實際可使用的記憶體,前後被兩個 size_t
整數包著,裡面各自紀錄著兩個 Magic Number,作為驗證這塊記憶體是否是由 test_malloc
分配的依據,以及作為記憶體是否有產生越界等不佳使用的依據。
位於記憶體之前的 magic number,實際上就是結構體中 magic_header
這個成員,其值會在 test_malloc
中被指定為 MAGICHEADER
,也就是 0xdeadbeef
; 在記憶體尾端的 size_t
整數,數值必定為 MAGICFOOTER
,也就是 0xbeefdead
。
因為要回傳給使用者的指標實際上是 payload
的位置,因在程式中另外有 find_footer
跟 find_header
這兩個函式作為輔助。前這傳入 block_ele_t*
,回傳位於 payload
尾端 magic number 的位址。如 test_malloc
中有一段指定尾端 magic number 的程式:
而後者則是傳入使用者呼叫後得到的記憶體開頭,反推該記憶體所屬的 block_ele_t
的開頭位置。這兩個函式除了用在 test_malloc
當中,也會用再 test_free
當中。在呼叫 test_free
時,使用者傳入的,實際上是 payload
的位置,但釋放記憶體時,除了該記憶體之外,該記憶體所屬的結構體,也要一併釋放。因此需要有一個尋找該記憶體所屬開頭的方法。
該二函數的原理不難理解。給定 block_ele_t
,尾端的 magic number,由 payload_size
進行推算即可; 對於反推記憶體所屬的結構體,則是利用 sizeof(block_ele_t)
反推,並且尋找反推過後的記憶體是否在 allocated
的清單中。若沒有,則推論為錯誤的配置。
而在 find_header
中有檢測傳入指標是否為空的設定。
我懷疑這是一個不能使用
calloc
跟strdup
的理由。但目前還沒確認該二函數中的malloc
是否有被影響到。
test_free
中用的原理類似:首先用 find_header
找出使用者準備釋放的記憶體,其所屬的結構體有沒有再 allocated
裡面。若傳入的指標為空指標,或是該記憶體不屬於任何一個節點,find_header
內部會分別傳出「試圖釋放空指標」或「記憶體損壞」等警告。
若順利找到該記憶體所屬的節點,接著檢驗尾端的 magic number 是否仍為 MAGICFOOTER
,作為記憶體有沒有被越界存取等狀況的判斷依據。若比對結果發現不合,也會發出該區段記憶體損壞的警告。
若確認結果無誤,則將記憶體區段前後的 magic number 都設成 MAGICFREE
。並且將記憶體內容都用 FHILLCHAR
填充。最後移出 allocated
這個 doubly-linked list。
不確定為什麼要先把 magic number 都改掉之後再釋放。我猜測是同樣大小的記憶體如果又被
malloc
,但裡面仍有 magic number 時,會造成誤判,以為不合法的記憶體其實是合法的。
在分配記憶體時,每呼叫一次 test_malloc
,allocate_count
就會 +1; 而每呼叫一次 test_free
,該數值就會 -1。因此程式即將結束時,判斷該數值,即可知道是否有妥當地釋放記憶體。
在 harness.c
中,有一些使用 sigsetjmp
, siglongjmp
的函式作為例外處理。該函式並未在 harness.c
中被其他函式呼叫。因此推測該例外處理用在其他地方。
qtest
,但應該要更有「品味」一點。決定再把上週的東西仔細看一遍,並嘗試影片裡面的技巧。malloc
的時候使用 malloc
」是一件很神奇的事,決定好好研究一下。calloc
及 strdup
使用 strdup
或 calloc
的話,測試程式會頻繁地跑出如:
CMU 助教為了測試使用 macro 將
malloc
和free
替換成test_malloc
和test_free
, 請看harness.h
和harness.c
, 所以你用其他配置記憶體的函數測試程式沒辦法正確測試
amikai
以及非常低的分數。後來在 qtest.c
中才發現:
雖然說明中有提及: " The function must explicitly allocate space and copy the string into it.",但不是很確定要求「明顯」到什麼程度。以至於花費不少時間懷疑人生。
free
依照 man
中的內容:
The free() function frees the memory space pointed to by ptr, which must have been returned by a previous call to malloc(), calloc(), or realloc(). Otherwise, or if free(ptr) has already been called before, undefined behavior occurs. If ptr is NULL, no operation is performed.
因此若 ptr
為 NULL
,則 free(ptr)
應該要合法。但以下實作:
在測試時將會出現如:
的錯誤訊息。