# 2023 lab-0
## q_new
```c
struct list_head *q_new()
{
struct list_head *head = malloc(sizeof(*head));
if (!head)
return NULL;
INIT_LIST_HEAD(head);
return head;
}
```
## q_size
des:
```c
int q_size(struct list_head *head)
{
if (!head)
return 0;
int len = 0;
struct list_head *li;
list_for_each (li, head)
len++;
return len;
}
```
## q_insert_head and q_insert_tail
```c=
bool q_insert_head(struct list_head *head, char *s)
{
if (!head)
return false;
element_t *node = malloc(sizeof(element_t));
if (!node)
return false;
size_t s_len = strlen(s) + 1;
node->value = malloc(s_len * sizeof(char));
if (!node->value)
return false;
memcpy(node->value, s, s_len);
list_add(&node->list, head);
return true;
}
```
兩者的實作方式基本上大同小異,差別只在於13行`q_insert_tail`裡使用的是`list_add_tail`但是在commit時噴:
```bash
$ git commit
Following files need to be cleaned up:
queue.c
queue.c:38:9: error: Memory leak: node [memleak]
return false;
^
queue.c:55:9: error: Memory leak: node [memleak]
return false;
^
Fail to pass static analysis.
```
參考:
https://hackmd.io/@25077667/lab0-2023 寫法 若malloc(7.22.3.1)失敗還是需要使用free釋放
對於`free`的描述(7.22.3.3):
>If ptr is a null pointer, no action occurs.
[c stard](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1548.pdf)
經過查證:
double free 產生的條件在記憶體釋放之後又重複做了一次釋放,要避免這一情況,可以在釋放之後將記憶體的指標位置設置為NULL
example:
```c
#include<stdio.h>
int main()
{
char *myptr = malloc(sizeof(char));
free(myptr);
free(myptr);
return 0;
}
```
避免產生double的情況:
```c
#include<stdio.h>
int main()
{
char *myptr = malloc(sizeof(char));
free(myptr);
myptr = NULL;
free(myptr);
return 0;
}
```
[reference](https://blog.gtwang.org/programming/memory-deallocation-issues-in-c/)
## q_free
```c
void q_free(struct list_head *l)
{
if(!l)
return;
element_t *node, *safe;
list_for_each_safe(node, safe, l) {
q_release_element(list_entry(node, element_t, list));
}
}
```
原本實作的方式為以上,但在使用`make check`後噴錯:
```bash=
ERROR: There is no queue, but 1 blocks are still allocated
# Exit program
Freeing queue
ERROR: Freed queue, but 1 blocks are still allocated
make: *** [Makefile:57: check] Error 1
```
顯示有記憶體沒有被釋放掉。
- 分析:
list_for_each_safe 定義:
```c
#define list_for_each_safe(node, safe, head) \
for (node = (head)->next, safe = node->next; node != (head); \
node = safe, safe = node->next)
```
以我帶入的參數:`list_for_each_safe(node, safe, l)` 展開得到以下:
`for (node = (l)->next, safe = node->next; node != (l); node = safe, safe = node->next)`
結果顯示l指標本身的指標並未被釋放掉,因此修改程式碼為:
```c
void q_free(struct list_head *l)
{
if(!l)
return;
struct list_head *node, *safe;
list_for_each_safe(node, safe, l) {
q_release_element(list_entry(node, element_t, list));
}
free(l);
}
```
## q_remove_tail &q_remove_head
des:移除queue的頭&尾部的element_t ,並將該element_t回傳
parameter:
algorithm:使用list_first_entry & list_last_entry 取得頭或尾的element指標,將element_t中的string設定到參數sp中
```c
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if (!head)
return NULL;
element_t *obj = list_first_entry(head, element_t, list);
size_t len = strlen(obj->value);
if (len > bufsize)
len = bufsize;
memcpy(sp, obj->value, len);
list_del(&obj->list);
return obj;
}
element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize)
{
if (!head)
return NULL;
element_t *obj = list_last_entry(head, element_t, list);
size_t len = strlen(obj->value);
if (len > bufsize)
len = bufsize;
memcpy(sp, obj->value, len);
list_del(&obj->list);
return obj;
}
```
## q_reverse
des:反轉整個queue的順序
algorithm:使用list_for_safe走遍整個queue並且交換next與prev的值
implement:
```c
void q_reverse(struct list_head *head)
{
struct list_head *safe;
struct list_head *node;
list_for_each_safe(node, safe, head) {
struct list_head *tmp = node->next;
node->next = node->prev;
node->prev = tmp;
}
}
```
## q_delete_mid
des:刪除串列中間的節點,n個節點則刪除n/2+1
eg.有三個節點則刪除第1個(最前面的節點為第0個),如果有四個節點則刪除第2個節點
algorithm:使用快慢指標
implement:
```c
bool q_delete_mid(struct list_head *head)
{
// https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/
if (!head || list_empty(head))
return false;
struct list_head **slow = &head->next;
for (struct list_head *fast = head->next; fast != head && fast->next != head; fast = fast->next->next) {
slow = &(*slow)->next;
}
list_del(*slow);
q_release_element(list_entry(*slow, element_t, list););
return true;
}
```
原本使用指向指標的指標來實作,直到跑test的時候才發現問題,錯誤訊息為:`Segmentation fault occurred. You dereferenced a NULL or invalid pointer`看了一下程式碼,表面上看起來演算法沒有什麼太大的問題,為了debug使用了printf分別印出幾個變數值之後才發現問題,首先修改程式碼如下:
```c=
bool q_delete_mid(struct list_head *head)
{
if (!head || list_empty(head))
return false;
struct list_head **slow = &head->next;
for (struct list_head *fast = head->next; fast != head && fast->next != head; fast = fast->next->next) {
slow = &(*slow)->next;
}
struct list_head *tmp = *slow;
printf("tmp 1 is %p\n",tmp);
printf("&tmp->prev->next 1 is %p\n",&tmp->prev->next);
printf("tmp->next 1 is %p\n",tmp->next);
printf("slow 1 is %p\n",slow);
printf("*slow 1 is %p\n",*slow);
list_del(tmp);
printf("tmp is %p\n",tmp);
printf("&tmp->prev->next is %p\n",&tmp->prev->next);
printf("tmp->next is %p\n",tmp->next);
printf("slow is %p\n",slow);
printf("*slow is %p\n",*slow);
element_t *tmpobj = list_entry(*slow, element_t, list);
q_release_element(tmpobj);
return true;
}
```
結果如下:
```bash=
tmp 1 is 0x7fffb9ef20f8
&tmp->prev->next 1 is 0x7fffb9ef19b0
tmp->next 1 is 0x7fffb9ef2188
slow 1 is 0x7fffb9ef19b0
*slow 1 is 0x7fffb9ef20f8
tmp is 0x7fffb9ef20f8
&tmp->prev->next is 0x7fffb9ef19b0
tmp->next is 0x7fffb9ef2188
slow is 0x7fffb9ef19b0
*slow is 0x7fffb9ef2188
```
首先使用了一個tmp指標存下前面運算得到的*slow指標,然後在使用list_del的前後分別印出幾個比較關鍵的數值
- 結論:slow由於是一個指標的指標指向的該記憶體位置裡面存放的記憶體因為list_del而被修改
- 原因:slow並不是直接指向那個需要被刪除的節點(之後稱這個節點為target節點)而是指向指向target節點的指標(也就是target前一個節點的next)在使用list_del之前存放的是target節點的位址,使用了list_del之後由於會更改到target節點前一個節點的next值(該值被指向target節點的下一個節點位址),因此在下一行執行`q_release_element`時會產生錯誤。