# 2019q3 Homework2 (lab0)
contributed by <`davinais`>
###### tags: `sysprog` `sysprog2019`
## 實驗環境
- 家中桌機
* OS: Linux Mint 17 3.13.0-139-generic
* gcc: 4.8.5
* 因為不只我在使用所以沒有隨便升級
## 作業要求
實作一個以 linked list 為基礎的 queue,將未完成的函數實作完成。
也就是將 `queue.h`、`queue.c` 中相關函數實作完成:
- `q_new`: 建立一個空的 queue
- `q_free`: free 掉一個 queue
- `q_insert_head`: 在 queue 的 head 處插入新元素(LIFO)
- `q_insert_tail`: 在 queue 的 tail 處插入新元素(FIFO)
- `q_remove_head`: 移除 queue 目前的 head 元素
- `q_size`: 計算 queue 中元素個數
- `q_reverse`: 反轉一個 queue ,其中不可 alloc 或 free 任何元素
## 函數實作
因為作業要求已經明確寫出是要去 `queue.h`、`queue.c` 將相關函數實作完成了,因此我先直接進入這兩個檔案完成作業之後,再來檢視整份作業。
`queue.h` 中定義了兩個 `struct`,分別是元素以及 queue 容器:
```c
/* Linked list element (You shouldn't need to change this) */
typedef struct ELE {
/* Pointer to array holding string.
This array needs to be explicitly allocated and freed */
char *value;
struct ELE *next;
} list_ele_t;
/* Queue structure */
typedef struct {
list_ele_t *head; /* Linked list of elements */
} queue_t;
```
我們可以發現這是個 Singular Linked List 的架構,再來檢視一下 `queue.c` ,看對所實作的函數有沒有其他要求。
最後發現本作業還要求 `q_insert_tail()` 以及 `q_size()` 都需要在 $O(1)$ 時間內完成,很明顯的以上述提供的結構是不可能達成這個任務的,我們還需要一點其他資料來輔助。
既然要使 `q_insert_tail()` 在常數時間內完成,可以用 Doubly Linked List 解決這個問題,但是我們並不能改變 `list_ele_t` 的結構。那麼還有一個方法也非常簡單,我們同時也**將 tail 記錄下來就好了**,因此我們在 `queue_t` 中新增一個 member `list_ele_t *tail` 來表示 tail。
那麼 queue 的大小要怎麼辦呢?也沒辦法從 head 跟 tail 的位址計算得出大小。那麼很簡單,**我們也把大小記錄下來**,在每次新增或刪除時更新就好了,因此再新增一個 member `size_t size`。
最後我們的 `queue_t` 便長這樣:
```c
typedef struct {
list_ele_t *head; /* Linked list of elements */
list_ele_t *tail;
size_t size;
} queue_t;
```
處理好 `queue` 的結構了,那我們可以開始實作函數了。
### `q_new()`
`q_new()` 一開始是有實作內容的:
```c
/*
Create empty queue.
Return NULL if could not allocate space.
*/
queue_t *q_new()
{
queue_t *q = malloc(sizeof(queue_t));
/* What if malloc returned NULL? */
q->head = NULL;
return q;
}
```
很明顯的註解還給我們提示了,如果 `malloc()` 回傳 `NULL` 該怎麼辦?**不怎麼辦,檢查是不是`NULL`啊!** 如果是的話就回傳 `NULL` ,反之則開始初始化 `queue_t`。記得還要將我們剛剛所增加的 member 也一併初始化:
```c
queue_t *q_new()
{
queue_t *q = malloc(sizeof(queue_t));
if(!q) {
return NULL;
}
q->head = NULL;
q->tail = NULL;
q->size = 0;
return q;
}
```
這裡將 head 以及 tail 都先設成 `NULL` 代表沒有東西,相當無腦且暴力的解決方案。
### `q_free()`
原本的實作相當的天真:
```c
/* Free all storage used by queue */
void q_free(queue_t *q)
{
/* How about freeing the list elements and the strings? */
/* Free queue structure */
free(q);
}
```
直接 free 掉 `q` 就當成 free 完成,很明顯是有問題的,連註解都提醒我們了:這樣處理的話我們可**完全沒有將 list 中的所有元素以及字串都 free 掉,問題可大了!** 這可不是有自動管理的記憶體配置系統呢!
解決的方式就是一個一個依序 free 完之後,再將 free 掉 `queue_t`,理論上可以正確的釋放所有 `malloc` 的空間,當然我們還是要先檢查 `q` 是不是 `NULL`:
```c
void q_free(queue_t *q)
{
if(!q) {
return;
}
list_ele_t *item = q->head;
list_ele_t *tmp = NULL;
while ((tmp = item)) {
free(item->value);
item = item->next;
free(tmp);
}
free(q);
}
```
### `q_insert_head()`
原本的實作為以下:
```c
/*
Attempt to insert element at head of queue.
Return true if successful.
Return false if q is NULL or could not allocate space.
Argument s points to the string to be stored.
The function must explicitly allocate space and copy the string into it.
*/
bool q_insert_head(queue_t *q, char *s)
{
list_ele_t *newh;
/* What should you do if the q is NULL? */
newh = malloc(sizeof(list_ele_t));
/* Don't forget to allocate space for the string and copy it */
/* What if either call to malloc returns NULL? */
newh->next = q->head;
q->head = newh;
return true;
}
```
基本上都被註解給提示光了,首先我們要先確認 `q` 是否為 `NULL`,再來檢查我們 alloc 的 `newh` 以及 `newh->value` 字元陣列是否有成功,最後才是進行 insert 操作。
```c=63
bool q_insert_head(queue_t *q, char *s)
{
if(!q) {
return false;
}
list_ele_t *newh;
/* What should you do if the q is NULL? */
newh = malloc(sizeof(list_ele_t));
/* Don't forget to allocate space for the string and copy it */
/* What if either call to malloc returns NULL? */
if (!newh) {
return false;
}
// Alloc for string
size_t len = (strlen(s) + 1);
newh->value = malloc(len);
if(!(newh->value)) {
return false;
}
memcpy(newh->value, s, len);
// Insert to head
newh->next = q->head;
// If the queue was empty, we need to revise tail too
if(q_is_empty(q)) {
q->tail = newh;
}
q->head = newh;
q->size++;
return true;
}
```
第 65~75 行是正常的檢查步驟,76~82行,我們 alloc 空間給 string 並且進行複製。
:::danger
這邊其實有點小 bug,參見 [執行自動評分](##執行自動評分)
:::
#### 為什麼不用 `strdup` 而使用 `memcpy`?
不使用 `strdup` 的原因是因為它是 POSIX 標準而非 C99 標準(7.21 一章沒有出現 `strdup`),可以的話我希望儘量以 C99 標準以內的東西完成,因此並沒有採用 `strdup`。使用 `strlen` 再加上結尾的 `'\0'` 作為 alloc 大小進行 `malloc`,再來進行後面 copy 的動作。
那麼為什麼在 copy 時選用 `memcpy` 而不是 `strcpy` 或者是 `strncpy` ?純粹是因為 **`strcpy` 以及 `strncpy` 在 copy 時還會檢查複製的 byte 是不是 `'\0'`** ,是的話會直接結束(參閱 C99 7.21.2),**在我們已經確定長度的情況下有些浪費時間**。在這種情況下採用 `memcpy` 我認為是比較有效率的作法。
:::info
由於 `strcpy` 與 `strncpy` 還要檢查被複製的 byte 是不是 `'\0'` ,我想應該只能一個 byte 一個 byte 依序複製,最佳化空間比較小;相比 `memcpy` 就不會有這個問題了,因此我認為在確定長度的情況下 `memcpy` 效能必定較佳。待驗證。
:::
最後就是進行 insert,因為我們還增加了 tail 這個 member,因此在 insert 時會有兩種情況:
- `q` 原本為空佇列,我們還需要設定 `q->tail` 為 `newh`
- `q` 非空佇列,不用改動 `q->tail`
因此我們還需檢查 `q` 是否為空佇列,在這裡我多實作了一個 `q_is_empty` 的 inline 函數以供後面也能直接調用檢查。
將 `newh->next` 設定為原本的 `head` ,並且更新完 `head` 之後,將 `q->size` 增加即完成 insert 的動作。
### `q_is_empty()`
這就是我上面提到的增加來檢查 `q` 是否為空佇列的函數,實作很簡單,其實只要檢查 `head` 、 `tail` 是否為 `NULL` ,或者直接檢查 `size` 是不是 0 就可以了:
```c
/*
Check if queue is empty
Return true if is empty.
Return false in other circumstances
*/
static inline bool q_is_empty(queue_t *q)
{
/* Check twice to avoid we only revised one of them when we call this func
*/
return (!(q->head) && q->size == 0);
}
```
檢查兩個條件是為了防呆,避免改到一半的時候呼叫此函數,~~不過其實沒什麼用啦因為這樣寫也不會有編譯錯誤~~,其實只要檢查一個就好了。
:::info
其實我有點在意這種部分到底是要讓 programmer 自己注意,還是要把防呆做好才行?
:::
### `q_insert_tail()`
這裡就真的要自己實作了,不過因為我們有紀錄 tail ,所以其實和 `q_insert_head()` 類似:
```c=95
/*
Attempt to insert element at tail of queue.
Return true if successful.
Return false if q is NULL or could not allocate space.
Argument s points to the string to be stored.
The function must explicitly allocate space and copy the string into it.
*/
bool q_insert_tail(queue_t *q, char *s)
{
/* You need to write the complete code for this function */
/* Remember: It should operate in O(1) time */
if (!q) {
return false;
}
list_ele_t *newt;
newt = malloc(sizeof(list_ele_t));
if (!newt) {
return false;
}
// Alloc for string
size_t len = (strlen(s) + 1);
newt->value = malloc(len);
if (!(newt->value)) {
return false;
}
memcpy(newt->value, s, len);
// Insert to tail
newt->next = NULL;
// If the queue was empty, we need to revise head too
// And also we only need to revise tail->next if q was not empty
if (q_is_empty(q)) {
q->head = newt;
} else {
q->tail->next = newt;
}
q->tail = newt;
q->size++;
return true;
}
```
:::danger
這邊其實有點小 bug,參見 [執行自動評分](##執行自動評分)
:::
前面到字串複製的部分都大同小異,唯一要注意的是仍舊需檢查 `q` 是否為空佇列:
- `q` 原本為空佇列,我們還需要設定 `q->head` 為 `newh`,而且不能設定 `q->tail->next` !(因為 `q->tail` 為 `NULL`)
- `q` 非空佇列,不用改動 `q->head`
最後再增加 `q->size` 即可。
### `q_remove_head()`
原本的實作真的單純只有移除 `head`:
```c
/*
Attempt to remove element from head of queue.
Return true if successful.
Return false if queue is NULL or empty.
If sp is non-NULL and an element is removed, copy the removed string to *sp
(up to a maximum of bufsize-1 characters, plus a null terminator.)
The space used by the list element and the string should be freed.
*/
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
/* You need to fix up this code. */
q->head = q->head->next;
return true;
}
```
很明顯的沒有那麼單純,而且我們還沒有複製字串呢。
加入對 q 的檢查以 return 正確的結果,並且將字串 copy 到 `sp` 中,最後再 free 掉需要 remove 的元素:
```c
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
/* You need to fix up this code. */
if ((!q) || q_is_empty(q)) {
return false;
}
list_ele_t *old_head = q->head;
q->head = q->head->next;
if (!!sp && bufsize > 0) {
memcpy(sp, old_head->value, bufsize - 1);
sp[bufsize - 1] = '\0';
}
free(old_head->value);
free(old_head);
q->size--;
return true;
}
```
要注意我們還需要檢查 `bufsize` 是否 > 0,不然會出錯。還要記得在最後一個 byte 手動補上 `'\0'` ,不然原本的字串比 `bufsize` 長的話,可能會導致存取越界造成 Segmentaion fault。
:::success
然而其實這邊還是有機會造成 Segmentaion fault 的,寫在 [評分不夠完整](##評分不夠完整)。
:::
### `q_size()`
很簡單,只要 q 不是 NULL 就直接回傳 `q->size` 就好:
```c
/*
Return number of elements in queue.
Return 0 if q is NULL or empty
*/
int q_size(queue_t *q)
{
/* You need to write the code for this function */
/* Remember: It should operate in O(1) time */
if (!q) {
return 0;
}
return q->size;
}
```
### `q_reverse()`
反轉其實不用多想,我們只要將整個 queue 遍歷過一遍,過程中記得將前一個元素以及目前的元素紀錄下來,再將目前元素的 `next` 更改連結到前一個元素即可,最後記得也要交換 `q->head` 以及 `q->tail`:
```c
/*
Reverse elements in queue
No effect if q is NULL or empty
This function should not allocate or free any list elements
(e.g., by calling q_insert_head, q_insert_tail, or q_remove_head).
It should rearrange the existing ones.
*/
void q_reverse(queue_t *q)
{
/* You need to write the code for this function */
if ((!q) || q_is_empty(q)) {
return;
}
list_ele_t *prev = NULL;
list_ele_t *tmp = NULL;
list_ele_t *current = q->head;
while (current) {
tmp = current->next;
current->next = prev;
prev = current;
current = tmp;
}
tmp = q->tail;
q->tail = q->head;
q->head = tmp;
}
```
## 執行自動評分
好了,一切看起來都很美好,但是執行起來不是這麼回事:
```
--- Trace Points
+++ TESTING trace trace-01-ops:
# Test of insert_head and remove_head
--- trace-01-ops 6/6
+++ TESTING trace trace-02-ops:
# Test of insert_head, insert_tail, and remove_head
--- trace-02-ops 6/6
+++ TESTING trace trace-03-ops:
# Test of insert_head, insert_tail, reverse, and remove_head
--- trace-03-ops 6/6
+++ TESTING trace trace-04-ops:
# Test of insert_head, insert_tail, and size
--- trace-04-ops 6/6
+++ TESTING trace trace-05-ops:
# Test of insert_head, insert_tail, remove_head reverse, and size
--- trace-05-ops 6/6
+++ TESTING trace trace-06-string:
# Test of truncated strings
--- trace-06-string 7/7
+++ TESTING trace trace-07-robust:
# Test operations on NULL queue
--- trace-07-robust 7/7
+++ TESTING trace trace-08-robust:
# Test operations on empty queue
--- trace-08-robust 7/7
+++ TESTING trace trace-09-robust:
# Test remove_head with NULL argument
--- trace-09-robust 7/7
+++ TESTING trace trace-10-malloc:
# Test of malloc failure on new
--- trace-10-malloc 7/7
+++ TESTING trace trace-11-malloc:
# Test of malloc failure on insert_head
ERROR: Freed queue, but 4 blocks are still allocated
--- trace-11-malloc 0/7
+++ TESTING trace trace-12-malloc:
# Test of malloc failure on insert_tail
ERROR: Freed queue, but 3 blocks are still allocated
--- trace-12-malloc 0/7
+++ TESTING trace trace-13-perf:
# Test performance of insert_tail
--- trace-13-perf 7/7
+++ TESTING trace trace-14-perf:
# Test performance of size
--- trace-14-perf 7/7
+++ TESTING trace trace-15-perf:
# Test performance of insert_tail, size, and reverse
--- trace-15-perf 7/7
--- TOTAL 86/100
```
是的,沒想到在測試 malloc_failure 的時候 (第11, 12個 script) 都出了問題。
該來看看 malloc_failure 是怎麼模擬的了,首先我們直接來看看在資料夾內搜尋 `malloc` 看看會調用什麼?
在 `harness.h` 內我們找到了以下代換:
```c=58
/* Tested program use our versions of malloc and free */
#define malloc test_malloc
#define free test_free
#define strdup test_strdup
```
因此我們所呼叫的 `malloc` 都會被代換成 `test_malloc`,從 [afcidk同學的開發紀錄](https://hackmd.io/@afcidk/ry4VZS9SN?type=view) 可以發現這是名為 `function hooking` 的技巧。之前在嵌入式系統上開發時(比如說 STM32、ARC等等),原廠所提供的 SDK 好像也有這種類似的技巧,讓我們可以用 `my_alloc` 、 `my_free` 或者是 `my_printf` 這種方式來呼叫我們自己修改過後的函數。
:::success
其實我記憶有點模糊不太確定是不是這樣,去翻以前的專案結果一時翻不到,待查證。
:::
:::info
是說我發現我自己也常常用的 `calloc` 被漏掉了,~~這樣可以鑽漏洞~~,應該要修改一下才對。
:::
觀察 `test_alloc` 裏面到底在賣什麼關子:
```c
void *test_malloc(size_t size)
{
if (noallocate_mode) {
report_event(MSG_FATAL, "Calls to malloc disallowed");
return NULL;
}
if (fail_allocation()) {
report_event(MSG_WARN, "Malloc returning NULL");
return NULL;
}
block_ele_t *new_block =
malloc(size + sizeof(block_ele_t) + sizeof(size_t));
if (new_block == NULL) {
report_event(MSG_FATAL, "Couldn't allocate any more memory");
error_occurred = true;
}
new_block->magic_header = MAGICHEADER;
new_block->payload_size = size;
*find_footer(new_block) = MAGICFOOTER;
void *p = (void *) &new_block->payload;
memset(p, FILLCHAR, size);
new_block->next = allocated;
new_block->prev = NULL;
if (allocated)
allocated->prev = new_block;
allocated = new_block;
allocated_count++;
return p;
}
```
看起來 malloc_failure 是來測試如果突然的錯誤回傳 `NULL` ,程式會怎麼回應,很顯然的我剛剛撰寫的程式並沒有完全解決這個問題。
### 直接執行第 11 個腳本
直接執行看看會怎樣:
```bash
$ ./qtest -f traces/trace-11-malloc.cmd
```
```
cmd># Test of malloc failure on insert_head
cmd>option fail 30
cmd>option malloc 0
cmd>new
q = []
cmd>option malloc 25
cmd>ih gerbil 20
WARNING: Malloc returning NULL
Insertion of gerbil failed
WARNING: Malloc returning NULL
Insertion of gerbil failed
WARNING: Malloc returning NULL
Insertion of gerbil failed
WARNING: Malloc returning NULL
Insertion of gerbil failed
WARNING: Malloc returning NULL
Insertion of gerbil failed
WARNING: Malloc returning NULL
Insertion of gerbil failed
WARNING: Malloc returning NULL
Insertion of gerbil failed
WARNING: Malloc returning NULL
Insertion of gerbil failed
WARNING: Malloc returning NULL
Insertion of gerbil failed
q = [gerbil gerbil gerbil gerbil gerbil gerbil gerbil gerbil gerbil gerbil gerbil]
cmd>
cmd>
cmd>
Freeing queue
ERROR: Freed queue, but 4 blocks are still allocated
```
看起來其實是 free 的錯誤而不是 insert 的錯誤,沒有完全的 free 乾淨。
### 天真地用 `printf` 抓錯
為了了解到底是哪裡出問題,我在 `test_free` 、 `test_alloc` 把 `allocated_count` 印出來,並且在 insert 、 free 成功時將字串印出,總算是找到問題點了。
:::success
用 gdb 抓錯或許會更快
:::
理論上每次 insert 成功的話應該要有兩次 alloc ,但是有時候只有一次,這就代表了 `newh`、`newt` alloc 成功,可是在 alloc 字串時失敗了!而且我在 alloc 字串失敗時,並沒有將剛剛 alloc 成功的 `newh`、`newt` free 掉,因此造成了 memory leak。
稍微修改一下便能解決這個問題:
```c=63
bool q_insert_head(queue_t *q, char *s)
{
if (!q) {
return false;
}
list_ele_t *newh;
/* What should you do if the q is NULL? */
newh = malloc(sizeof(list_ele_t));
/* Don't forget to allocate space for the string and copy it */
/* What if either call to malloc returns NULL? */
if (!newh) {
return false;
}
// Alloc for string
size_t len = (strlen(s) + 1);
newh->value = malloc(len);
if (!(newh->value)) {
free(newh);
return false;
}
memcpy(newh->value, s, len);
// Insert to head
newh->next = q->head;
// If the queue was empty, we need to revise tail too
if (q_is_empty(q)) {
q->tail = newh;
}
q->head = newh;
q->size++;
return true;
}
/*
Attempt to insert element at tail of queue.
Return true if successful.
Return false if q is NULL or could not allocate space.
Argument s points to the string to be stored.
The function must explicitly allocate space and copy the string into it.
*/
bool q_insert_tail(queue_t *q, char *s)
{
/* You need to write the complete code for this function */
/* Remember: It should operate in O(1) time */
if (!q) {
return false;
}
list_ele_t *newt;
newt = malloc(sizeof(list_ele_t));
if (!newt) {
return false;
}
// Alloc for string
size_t len = (strlen(s) + 1);
newt->value = malloc(len);
if (!(newt->value)) {
free(newt);
return false;
}
memcpy(newt->value, s, len);
// Insert to tail
newt->next = NULL;
// If the queue was empty, we need to revise head too
// And also we only need to revise tail->next if q was not empty
if (q_is_empty(q)) {
q->head = newt;
} else {
q->tail->next = newt;
}
q->tail = newt;
q->size++;
return true;
}
```
在第 80 行與 118 行補上 free ,問題解決。
重新測試一次,果然功德圓滿,100 分結束。
### 評分不夠完整
功德圓滿才怪,後來檢查才發現 `q_remove_head()` 忘記了如果刪除完最後變空的, `q->tail` 也要指回 `NULL` 才行。看來這個測試沒有完整含括到所有情況。
## `MAGICHEADER` 、 `MAGICFOOTER` 、 `MAGICFREE` 以及 `FILLCHAR` 的運用
在`harness.c` 中,可以發現到 `MAGICHEADER` 、 `MAGICFOOTER` 、 `MAGICFREE` 以及 `FILLCHAR` 這幾個巨集的應用,主要與自訂的 memory block 型態一同使用:
```c
/** Special values **/
/* Value at start of every allocated block */
#define MAGICHEADER 0xdeadbeef
/* Value when deallocate block */
#define MAGICFREE 0xffffffff
/* Value at end of every block */
#define MAGICFOOTER 0xbeefdead
/* Byte to fill newly malloced space with */
#define FILLCHAR 0x55
```
```c
typedef struct BELE {
struct BELE *next;
struct BELE *prev;
size_t payload_size;
size_t magic_header; /* Marker to see if block seems legitimate */
unsigned char payload[0];
/* Also place magic number at tail of every block */
} block_ele_t;
```
它採用了 Doubly-Linked List 的架構管理 memory block,可以方便地找到所有目前已 allocated 的 block,還有一個 `magic_header` 欄位拿來存放 `MAGICHEADER` ,可以在 `test_malloc` 發現它的存在:
```c=120
void *test_malloc(size_t size)
{
if (noallocate_mode) {
report_event(MSG_FATAL, "Calls to malloc disallowed");
return NULL;
}
if (fail_allocation()) {
report_event(MSG_WARN, "Malloc returning NULL");
return NULL;
}
block_ele_t *new_block =
malloc(size + sizeof(block_ele_t) + sizeof(size_t));
if (new_block == NULL) {
report_event(MSG_FATAL, "Couldn't allocate any more memory");
error_occurred = true;
}
new_block->magic_header = MAGICHEADER;
new_block->payload_size = size;
*find_footer(new_block) = MAGICFOOTER;
void *p = (void *) &new_block->payload;
memset(p, FILLCHAR, size);
new_block->next = allocated;
new_block->prev = NULL;
if (allocated)
allocated->prev = new_block;
allocated = new_block;
allocated_count++;
return p;
}
```
第 136 行將 `MAGICHEADER` 指給 `magic_header`,目的應該是為了標定我們所提供的指標真的是由 `test_malloc` 所 alloc 出來的 memory,我想同時也能檢查有沒有簡單的存取越界問題。如果在檢查 `magic_header` 的內容時發現內容並不是 `MAGICHEADER`,那麼不是我們所給的指標並不是 allocated 出來的 memory,就是我們不小心改到不合法位址造成 memory block 損壞了。
`MAGICFOOTER` 應該也是同理,用來檢測 memory block 是否為 allocated 或者損毀。
:::info
不過這裡決定 footer 的方式是先將 `block_ele_t *` 轉成 `size_t` 之後再與其他 size 做相加,最後再轉回 `size_t *` ,不知道這麼做的用意為何?
:::
`FILLCHAR` 在第 140 行出現,我們以該值 `0x55` 填滿整個 memory block 的 payload 部分。我們知道 $55_{16} = 01010101_{2}$,我認為這是特別將 memory 的每個 byte 都設成 1 ,來考驗大家會不會記得在 `q_remove_head` 複製字串的時候,要記得把最後一個 byte 設成 `'\0'` 。
:::info
但是我認為他在 `test_free` 中有點多餘,
```c=168
b->magic_header = MAGICFREE;
*find_footer(b) = MAGICFREE;
memset(p, FILLCHAR, b->payload_size);
```
有一種可能性是重新清空以避免意外調用然後發現資料還能使用,保障資料安全問題...,好吧這麼說來好像有一點用。
:::
`MAGICFREE`,顧名思義只出現在 `test_free` 中:
```c=150
void test_free(void *p)
{
if (noallocate_mode) {
report_event(MSG_FATAL, "Calls to free disallowed");
return;
}
if (p == NULL) {
return;
}
block_ele_t *b = find_header(p);
size_t footer = *find_footer(b);
if (footer != MAGICFOOTER) {
report_event(MSG_ERROR,
"Corruption detected in block with address %p when "
"attempting to free it",
p);
error_occurred = true;
}
b->magic_header = MAGICFREE;
*find_footer(b) = MAGICFREE;
memset(p, FILLCHAR, b->payload_size);
/* Unlink from list */
block_ele_t *bn = b->next;
block_ele_t *bp = b->prev;
if (bp)
bp->next = bn;
else
allocated = bn;
if (bn)
bn->prev = bp;
free(b);
allocated_count--;
}
```
我猜測可能是故意先用此值將 `magic_header` 以及 `footer` 改掉,避免我們之後不小心把已經 free 掉的 block 的指標傳進去的時候,在對應 `magic_header` 位址的部分剛好殘餘舊值,導致程式誤會成這是還沒被 `free` 掉的 block。
## Reference
- [afcidk同學的開發紀錄](https://hackmd.io/@afcidk/ry4VZS9SN?type=view)