---
# System prepended metadata

title: 2020q3 Homework1 (lab0)

---

# 2020q3 Homework1 (lab0)
contributed by < `Weiting0114` >

## 簡介
lab0 中主要是透過許多工具，讓我們養成良好的寫程式習慣，包含：
* Git pre-commit hook
    * [git-good-commit](https://github.com/tommarshall/git-good-commit): 學習撰寫良好的 Git commit message
    * [cppcheck](http://cppcheck.sourceforge.net/): 靜態程式碼分析工具
    * [clang-format](https://clang.llvm.org/docs/ClangFormat.html): 確保一致的程式風格
    * [GNU Aspell](http://aspell.net/): 拼字檢查
* [valgrind](https://valgrind.org/): 動態程式分析工具
* [AddressSanitizer](https://github.com/google/sanitizers/wiki/AddressSanitizer): 檢查記憶體的使用

詳細的環境設定以及lab的相關說明請參閱 [I01: lab0](https://hackmd.io/@sysprog/2020-lab0?fbclid=IwAR2YOjtMYzE_kB5vqvBUOSM9OlR5hgvz7NHUGg21NOcN9Hb8vccxEV-yC3o)
[Github 程式碼](https://github.com/Weiting0114/lab0-c)

## 環境
```lang=shell
$ uname -a
Linux eric 5.4.0-47-generic #51~18.04.1-Ubuntu SMP Sat Sep 5 14:35:50 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

$ gcc --version
gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
```

## 作業要求
於 `queue.[c/h]` 中完成下列功能
* `q_new`: 建立新的「空」佇列;
* `q_free`: 釋放佇列所佔用的記憶體;
* `q_insert_head`: 在佇列開頭 (head) 加入 (insert) 給定的新元素 (以 LIFO 準則);
* `q_insert_tail`: 在佇列尾端 (tail) 加入 (insert) 給定的新元素 (以 FIFO 準則);
* `q_remove_head`: 在佇列開頭 (head) 移除 (remove) 給定的元素;
* `q_size`: 計算佇列中的元素數量;
* `q_reverse`: 以反向順序重新排列鏈結串列，只能重排已經存在的元素;
* `q_sort`: 以==遞增順序==來排序鏈結串列的元素
* 功能需要在 $O(1)$ 時間內執行

## 開發過程
#### **`queue.h`**
* 將 `queue_t` 結構體增加兩位成員，`tail`、`size`。增加 `list_ele_t *tail` 提供Queue快速尋訪，達成執行時間需在 $O(1)$ 內執行完畢。
```c=
/* Queue structure */
typedef struct {
    list_ele_t *head; /* Linked list of elements */
    list_ele_t *tail;
    int size; 
} queue_t;
```

#### **`q_new`**
* 透過判斷式確認記憶體分配是否成功，避免空指標的操作。
```c=
queue_t *q_new()
{
    queue_t *q = malloc(sizeof(queue_t));
    if (q){
        q->head = NULL;
        q->tail = NULL;
        q->size = 0;
    }
    return q;
}
```

#### **`q_free`**
* 利用新節點 `tmp` 儲存要釋放的資料
* 透過 `q->head` 的移動，將整個 `queue` 清空
>在清除節點時須先釋放資料再釋放節點，否則記憶體中會殘留資料
```c=
void q_free(queue_t *q)
{
    if (q == NULL)
        return;

    while (q->head) {
        list_ele_t *temp;
        temp = q->head;
        q->head = q->head->next;
        free(temp->value);
        free(temp);
    }
    free(q);
}
```


#### **`q_insert_head`**
* 將新元素 `newh` 插入 `queue` 的最前端
* 判斷 `queue` 是否為 `NULL` ，避免在之後的計算上造成 memory leak
* 分配記憶體給新的元素(`newh`)，判斷是否分配成功，若失敗，則將之前分配的 `newh` 清除
* 利用 C library <string.h> 的 strncpy 複製 s，並存入 `newh->value`
* 在新增第一個元素時，須將 `q->head`、 `q->tail` 指向新插入的元素。之後隨著插入元素的增加，將 `q->head` 逐漸向後移
:::info
:exclamation: 下列是未判斷 `queue` 是否為 `NULL`，會在 `trace-07-robust` 出錯，利用 valgrid 查看發現是 memory leak。
```lang = shell
$ valgrind --memcheck:show-leak-kinds=all ./qtest < traces/trace-07-robust.cmd

==5649== Address 0x10 is not stack'd, malloc'd or (recently) free'd
==5649== Segmentation fault occurred.  You dereferenced a NULL or invalid pointer

...

==5649== HEAP SUMMARY:
==5649==    in use at exit: 9,165 bytes in 30 blocks
==5649==    total heap usage: 53 allocs, 23 frees, 10,442 bytes allocated
==5649== 
==5649== LEAK SUMMARY:
==5649==    definitely lost: 0 bytes in 0 blocks
==5649==    indirectly lost: 0 bytes in 0 blocks
==5649==    possibly lost: 0 bytes in 0 blocks
==5649==    still reachable: 9,165 bytes in 30 blocks
==5649==    suppressed: 0 bytes in 0 blocks
```
:::


```cpp=
bool q_insert_head(queue_t *q, char *s)
{
    if (q == NULL)
        return false;

    list_ele_t *newh;
    newh = malloc(sizeof(list_ele_t));
    if (newh == NULL)
        return false;

    newh->value = malloc(sizeof(char) * strlen(s) + 1);
    if (newh->value == NULL) {
        free(newh);
        return false;
    }
    strncpy(newh->value, s, strlen(s) + 1);

    if (q->size == 0) {
        newh->next = NULL;
        q->head = q->tail = newh;
    } else {
        newh->next = q->head;
        q->head = newh;
    }
    q->size += 1;
    return true;
}
```

#### **`q_insert_tail`**
* 作法與 `q_insert_head` 差不多
* 將新元素 (`newt`) 放入 `queue` 的尾端
* 若插入為第一個元素，則將 head 和 tail 同時指向新元素 (`newt`)

```c=
bool q_insert_tail(queue_t *q, char *s)
{
    if (q == NULL)
        return false;

    list_ele_t *newt;
    newt = malloc(sizeof(list_ele_t));
    if (newt == NULL)
        return false;

    newt->value = malloc(sizeof(char) * strlen(s) + 1);
    if (newt->value == NULL) {
        free(newt);
        return false;
    }

    strncpy(newt->value, s, strlen(s) + 1);

    if (q->size == 0) {
        q->head = q->tail = newt;
    } else {
        q->tail->next = newt;
        q->tail = newt;
    }
    newt->next = NULL;
    q->size += 1;
    return true;
}
```

#### **`q_remove_head`**
* 藉由確認 `q->head` 是否為 `NULL`，確認 `queue` 是否為空
* 若 `sp` 不為 `NULL`，將要刪除後的字串複製進去
* 新增一個指標 `rm_node` 以供釋放 (`free`) 
* 將 `rm_node` 的成員都清空後，再釋放 `rm_node`
```c=
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
    if (q == NULL || q->head == NULL || q->size == 0)
        return false;

    list_ele_t *rm_node = q->head;
    if (sp) {
        memset(sp, '\0', bufsize);
        strncpy(sp, rm_node->value, bufsize - 1);
    }

    /*
     *Method as same as free function
     *Free node's value and then free value
     */
    q->head = q->head->next;
    q->size -= 1;
    free(rm_node->value);
    free(rm_node);

    return true;
}
```
:::info
:exclamation: 在 `trace-06-string` 測試中出現了error，原因是因為沒有設定字串的結束 ( `'\0'`)，因此利用 memset 將 `sp` 的內容全部寫為零。
```cpp=8
memset(sp, '\0', bufsize);
```


<!-- * 利用valgrind進行測試:
```shell
$ valgrind --memcheck:show-leak-kinds=all ./qtest < traces/trace-06-string.cmd
``` -->
> ERROR: Removed value aardvark_bear_dolphin_gerbil_jXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX != expected value meerkat_panda_squirrel_vulture
:::

#### **`q_size`**
* 若 `q` 不存在，則返回 `0`
* 藉由直接取得 `q->size`，達成在 $O(1)$ 時間內完成 `q_size()`
* 在 `q_new` 時，便已將 `q->size` 設為 `0` ，因此就算 queue 為空，還是會返回 `0`
```c=
int q_size(queue_t *q)
{
    if (q == NULL)
        return 0;
    return q->size;
}
```

#### **`q_reverse`**
* 利用 `q->size` 檢查 queue 是否有足夠的資料可以做反轉
* 將 `q->tail` 設定為 `q->head`，利用 `curnxt` 來儲存 `q->head->next` ，避免待會將資料反向後，無法取得下一筆資料 
```cpp=
void q_reverse(queue_t *q)
{
    if (q == NULL || q->size <= 1)
        return;

    list_ele_t *cur, *curnxt;
    q->tail = q->head;
    cur = q->head;
    curnxt = q->head->next;
    
    while (curnxt != NULL) {
        q->head = curnxt;
        curnxt = q->head->next;
        q->head->next = cur;
        cur = q->head;
    }
    q->tail->next = NULL;
}
```
* 原始 queue 的狀況
```graphviz
digraph linked_list{
    rankdir=LR;
    node [shape=record];
    a [label="{ <data> a | <ref>  }"]
    b [label="{ <data> b | <ref>  }"];
    c [label="{ <data> c | <ref>  }"];
    d [label="{ <data> d | <ref>  }"];
    
    a:ref:c -> b:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false, arrowsize=1.2];
    b:ref:c -> c:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
    c:ref:c -> d :data     [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
    
    d:ref:d -> NULL :data     [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
    
    head [shape=box];
    head -> a;
    tail [shape=box];
    tail -> d;
}
```
* 進入迴圈之前 queue 的狀況
```graphviz
digraph linked_list{
    rankdir=LR;
    node [shape=record];
    a [label="{ <data> a | <ref>  }"]
    b [label="{ <data> b | <ref>  }"];
    c [label="{ <data> c | <ref>  }"];
    d [label="{ <data> d | <ref>  }"];
    
    a:ref:c -> b:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false, arrowsize=1.2];
    b:ref:c -> c:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
    c:ref:c -> d:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
    d:ref:d -> NULL:data[arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
    
    cur -> a;
    curnxt -> b;
    head [shape=box];
    head -> a;
    tail [shape=box];
    tail -> a;
}
```

* 進入迴圈之後將 `q->head` 、 `curnxt` 向後移動
* 並將兩兩元素做交換 

<pre>
    while (curnxt != NULL) {
        <span style="color:blue">q->head = curnxt;</span>
        <span style="color:blue">curnxt = q->head->next;</span>
        <span style="color:red">q->head->next = cur;</span>
        <span style="color:red">cur = q->head;</span>
    }
    <span style="color:green">q->tail->next = NULL;</span>
</pre>

下圖<font color=blue>藍色</font>部份是為了將整個 `queue` 走過一遍，<font color=red>紅色</font>的部份是將 `node` 做反轉：

```graphviz
digraph linked_list{
    rankdir=LR;
    node [shape=record];
    a [label="{ <data> a | <ref>  }"]
    b [label="{ <data> b | <ref>  }"];
    c [label="{ <data> c | <ref>  }"];
    d [label="{ <data> d | <ref>  }"];
    
    a:ref:a -> b:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
    b:ref:b -> c:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
    c:ref:c -> d:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
    d:ref:d -> NULL[arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
    
    cur -> a;
    curnxt [fontcolor=blue, shape=box, color=blue]
    curnxt -> c [color=blue];
    head [fontcolor=blue, shape=box, color=blue];
    head -> b [color=blue];
    tail [shape=box];
    tail -> a;
}
```

```graphviz
digraph linked_list{
    rankdir=LR;
    node [shape=record];
    a [label="{ <data> a | <ref>  }"];
    b [label="{ <data> b | <ref>  }"];
    c [label="{ <data> c | <ref>  }"];
    d [label="{ <data> d | <ref>  }"];
    head [fontcolor=blue, color=blue];
    cur [label="{cur}"][fontcolor=red, color=red];
    curnxt [label="{curnxt}"][fontcolor=blue, color=blue];
    
    a:ref:c -> b [arrowtail=dot, dir=both, tailclip=false];
    b:ref:c -> a:data [arrowtail=dot, dir=both, tailclip=false] [color=red];
    cur  -> b [color=red];
    head -> b [color=blue];
    
    tail -> a;
    curnxt -> c:data [color=blue];
    c:ref:c -> d [arrowtail=dot, dir=both, tailclip=false];
    d:ref:d -> NULL [arrowtail=dot, dir=both, tailclip=false];
}
```


#### **`q_sort`**
* 參考 [Linked LIst Sort](https://npes87184.github.io/2015-09-12-linkedListSort/)，使用 Merge sort 排序來完成
* `merge_sort()`
    * 將一個 Linked list 拆成兩個
        * 藉由兩個指標 (`fast`, `slow`) 不同的前進速度 (`fast` : `slow` = 1 : 2)，進而找出位於中間的節點。演算法是利用[龜兔賽跑 (Floyd’s algorithm)](https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm) 
        * `fast` 會指向最後的 `element`
        * `slow` 會指向中間的 `element`
        * 將 `fast` 指向 `slow->next` 後，便可以得到兩個等長的 `linked list` (`head` 和 `fast`)
    * 將兩個已排序的 Linked list 合成一個 Linked list，並回傳
        * 參考 [jwang0306](https://hackmd.io/@jwang0306/lab0-c) 的 `merge`，將比較字串的部分縮進 `while`
        * 遍歷 `l1` 和 `l2`，並比較兩者元素，來建立新 Linked list
```cpp=
void q_sort(queue_t *q)
{
    if (q == NULL || q->size <= 1)
        return;

    q->head = split(q->head);

    while (q->tail) {
        q->tail = q->tail->next;
    }
}
```

**`split`**
``` cpp=
list_ele_t *split(list_ele_t *head)
{
    if (head == NULL || head->next == NULL)
        return head;

    /*Split list*/
    list_ele_t *slow = head, *fast;
    for (fast = head->next; fast && fast->next; fast = fast->next->next) {
        slow = slow->next;
    }
    fast = slow->next;
    slow->next = NULL;

    // sort each list
    list_ele_t *left = split(head);
    list_ele_t *right = split(fast);

    // merge sorted left and sorted right
    return merge(left, right);
}
```

**`merge`**
``` cpp=
list_ele_t *merge(list_ele_t *left, list_ele_t *right)
{
    list_ele_t *head = NULL;
    list_ele_t **tmp = &head;

    while (left && right) {
        if (strcmp(left->value, right->value) < 0) {
            *tmp = left;
            left = left->next;
        } else {
            *tmp = right;
            right = right->next;
        }
        tmp = &((*tmp)->next);
    }

    if (left)
        *tmp = left;
    if (right)
        *tmp = right;
    return head;
}
```

:::info 
[你所不知道的C語言: linked list 和非連續記憶體操作](https://hackmd.io/@sysprog/c-linked-list?type=view)

Linus Torvalds 的解說:
> People who understand pointers just use a "pointer to the entry pointer", and initialize that with the address of the list_head. And then as they traverse the list, they can remove the entry without using any conditionals, by just doing`*pp = entry->next`.

因此我們先做一個指向 head 的指標: `Node **indirect = &list->head;`
```mermaid
graph LR
    subgraph linked list
        head==>node1==>node2
    end
    subgraph pointer to pointer
        indirect==>head
    end
```

在走訪 list 的時候，先`*indirect` (dereference `indirect` ，找出它所指向的 node ，此刻為 head)， 接著```(*tmp)->next```(找出它的 `->next` ，這邊為 head 的 next ，也就是 node1)，然後 `indirect = &((*indirect)->next)` (將其 reference 存回 `indirect` ，此刻 `indirect` 就變為指向 node1 的指標)：

```mermaid
graph LR
    subgraph linked list
        head==>node1==>node2
    end
    subgraph pointer to pointer
        indirect==>node1
    end
```
:::

## 指標的指標
* 擷取自[你所不知道的 C 語言：指標篇](https://reurl.cc/EzmAVa)
* 利用下列兩段程式碼解說為何要使用指標的指標

考慮以下程式碼:
```cpp=1
int B = 2;
void func(int *p) { p = &B; }
int main() {
    int A = 1, C = 3;
    int *ptrA = &A;
    func(ptrA);
    printf("%d\n", *ptrA);
    return 0;
}
```

在第 5 行 (含) 之前的記憶體示意:
```graphviz
digraph structs {
	node[shape=record]
    {rank=same; structa,structb,structc}
    structptr [label="ptrA|<ptr> &A"];
	structb [label="<B> B|2"];
    structa [label="<A> A|1"];
    structc [label="<C> C|3"]; 	
	
    structptr:ptr -> structa:A:nw
}
```

第 6 行 `ptrA` 數值傳入 `func` 後的記憶體示意:
```graphviz
digraph structs {
	node[shape=record]
    {rank=same; structa,structb,structc}
    
    structp [label="p|<p>&A"]
    structptr [label="<name_ptr> ptrA|<ptr> &A"];
	structb [label="<B> B|2"];
    structa [label="<A> A|1"];
    structc [label="<C> C|3"]; 	
	
    structptr:ptr -> structa:A:nw
    structp:p -> structa:A:nw
    
}
```

`func` 將變數 `p` 的值指定為 `&B`
```graphviz
digraph structs {
	node[shape=record]
    {rank=same; structa,structb,structc}
    structp [label="p|<p>&B"]
    structptr [label="<name_ptr> ptrA|<ptr> &A"];
	structb [label="<B> B|2"];
    structa [label="<A> A|1"];
    structc [label="<C> C|3"]; 	
	
    structptr:ptr -> structa:A:nw
    structp:p -> structb:B:nw  
}
```

由上圖可見，原本在 main 中的 `ptrA` 內含值沒有改變。這不是我們期望的結果，該如何克服呢？可透過「指標的指標」來改寫，如下：

```cpp=1
int B = 2;
void func(int **p) { *p = &B; }
int main() {
    int A = 1, C = 3;
    int *ptrA = &A;
    func(&ptrA);
    printf("%d\n", *ptrA);
    return 0;
}
```

在第 5 行 (含) 之前的記憶體示意:
```graphviz
digraph structs {
	node[shape=record]
    {rank=same; structa,structb,structc}
    structptr [label="ptrA|<ptr> &A"];
	structb [label="<B> B|2"];
    structa [label="<A> A|1"];
    structc [label="<C> C|3"]; 	
	
    structptr:ptr -> structa:A:nw
}
```

第 6 行時，傳入 `func` 的參數是 `&ptrA`，也就是下圖中的 `&ptrA(temp)`:
```graphviz
digraph structs {
	node[shape=record]
    {rank=same; structa,structb,structc}
    structadptr [label="&ptrA(temp)|<adptr> &ptrA"]
    structptr [label="<name_ptr> ptrA|<ptr> &A"];
	structb [label="<B> B|2"];
    structa [label="<A> A|1"];
    structc [label="<C> C|3"]; 	
	
    structptr:ptr -> structa:A:nw
    structadptr:adptr -> structptr:name_ptr:nw
}
```

進入 func 執行後，在編譯器產生對應參數傳遞的程式碼中，會複製一份剛傳入的 `&ptrA`，產生一個自動變數 `p`，將 `&ptrA` 內的值存在其中，示意如下:
```graphviz
digraph structs {
	node[shape=record]
    {rank=same; structa,structb,structc}
    structp [label="p(in func)|<p> &ptrA"]
    structadptr [label="&ptrA(temp)|<adptr> &ptrA"]
    structptr [label="<name_ptr> ptrA|<ptr> &A"];
	structb [label="<B> B|2"];
    structa [label="<A> A|1"];
    structc [label="<C> C|3"]; 	
	
    structptr:ptr -> structa:A:nw
    structadptr:adptr -> structptr:name_ptr:nw
    structp:p -> structptr:name_ptr:nw
}
```

在 `func` 中把 `p` 指向到的值換成 `&B`:
```graphviz
digraph structs {
	node[shape=record]
    {rank=same; structa,structb,structc}
    structp [label="p(in func)|<p> &ptrA"]
    structadptr [label="&ptrA(temp)|<adptr> &ptrA"]
    structptr [label="<name_ptr> ptrA|<ptr> &B"];
	structb [label="<B> B|2"];
    structa [label="<A> A|1"];
    structc [label="<C> C|3"]; 	
	
    structptr:ptr -> structb:B:nw
    structadptr:adptr -> structptr:name_ptr:nw
    structp:p -> structptr:name_ptr:nw
}
```

經過上述「指標的指標」，`*ptrA` 的數值從 1 變成 2，而且 `ptrA` 指向的物件也改變了。


## Valgrind - Massif
* 環境設定: 安裝 [Massif](https://reurl.cc/GrAN13) `$ sudo apt install massif-visualizer`
* 首先執行以下命令，這裡使用 `trace-06-string.cmd`作為範例
```shell
$ valgrind --tool=massif ./qtest < traces/trace-06-string.cmd
```
* 在該資料夾可得到記憶體資料的文字檔 `massif.out.<pid>` ，執行下列指令
```shell
$ ms_print massif.out.16246
```
:::info
:exclamation: `pid` 會根據執行 valgrind 時的執行緒改變，因此要注意其對應的編號
:::

* 執行完 `ms_print massif.out.16246` 即可得到下列結果
![](https://i.imgur.com/0m24yrL.png)


