Chongzhi314
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # 2025q1 Homework1 (lab0) contributed by <[JimmyChongz](https://github.com/JimmyChongz)> {%hackmd NrmQUGbRQWemgwPfhzXj6g %} ## 安裝 Ubuntu 24.04 [在 Windows 11 安裝 Ubuntu 24.04.1 LTS](https://hackmd.io/@Jimmy-Xu/SkAXnK7LJx) ## 環境 ```bash $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 24.04.2 LTS Release: 24.04 Codename: noble $ gcc --version gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 ``` ## queue.c 的疑難雜症 ### q_new > commit: [ab7b291](https://github.com/sysprog21/lab0-c/commit/ab7b291b65738ade55561cbd638d73ac36378d02) #### 我沒注意記憶體分配失敗的處理。 記憶體配置不能保證成功,而是可能返回 null 指標。若直接使用 malloc 回傳的值而不檢查分配是否成功,則有可能會造成 segmentation fault on the null pointer dereference。 ### q_free > commit: [1f7493b](https://github.com/sysprog21/lab0-c/commit/1f7493b5451b8462a81e44668a6a608ca2c1539c) #### 怎麼完整 free 掉整個 Linked List 而不會發生 Memory leak ? 使用迴圈來釋放鏈結串列中所有的節點。另外,若 `struct` 中含有**動態配置的記憶體指標**,則要先 `free` 該指標所指向的記憶體空間,再 `free` 該結構體。還有 `head` 的結構與節點不同,`head` 並沒有 `element_t` 結構,故只需單獨 `free`。 ```clike free(list_entry(tmp, element_t, list)->value); free(list_entry(tmp, element_t, list)); ``` 善用 Linux kernel API `q_release_element` > commit: [84ee2f1](https://github.com/sysprog21/lab0-c/commit/84ee2f1407da8bccdea176d0be8baa2ec59c943d) ### q_insert > commit: [a202b1e](https://github.com/sysprog21/lab0-c/commit/a202b1e11ecb64ddb73a5e9f22d65e7a3651489c) > #### 為什麼不能直接 newNode->value = s ? Ans: 這樣只會讓 `newNode->value` 指向 `s`,但不會複製內容,若之後修改 `s`,那 `newNode->value` 也會跟著改變。例如: ```c char *s; char str[] = "Hello World!"; s = str; printf("s: %s, str: %s\n", s, str); strcpy(str, "123"); printf("s: %s, str: %s\n", s, str); ``` ``` // output s: Hello World!, str: Hello World! s: 123, str: 123 ``` 又或者當 s 被釋放(即 free(s)),而 s 與 `newNode->value` 都指向同一塊記憶體空間,此時 `newNode->value` 就會變成 **dangling pointer**。如果之後程式碼使用 `newNode->value`,就會出現不可預期的錯誤。 #### 那怎麼解決? 解決方法:使用 `strdup` 或 `strncpy` 來賦值給 `newNode->value` - `strdup(s)` : 複製傳入參數 `s` 指向的字串到一塊新的記憶體空間。意思就是說此函數會先 malloc `s` 指向字串的 size,再透過 `memcpy` 將 `s` 指向的字串複製到先前 malloc 的記憶體空間。 ![截圖 2025-02-27 上午10.09.20](https://hackmd.io/_uploads/H12UKHT5Jx.png) - `strncpy(str1, str2, n)` : 將 `str2` 的前 `n` 個字元複製到 `str1` 中。要注意預留一個 byte 的空間給 null character。 ![截圖 2025-02-27 上午10.44.42](https://hackmd.io/_uploads/rkYsbUTqJe.png) #### 那 `strdup` 跟 `strncpy` 用哪個會比較好? `strdup` 比較好,因為 `strdup` 會自動分配記憶體,而 `strncpy` 只是單純複製字串,**但仍然需要手動分配記憶體**,例如: ```c newNode->value = malloc(strlen(s) + 1); // 需要自己分配記憶體 strncpy(newNode->value, s, strlen(s)); newNode->value[strlen(s)] = '\0'; // 確保結尾有 '\0' ``` ### q_remove > commit: [a202b1e](https://github.com/sysprog21/lab0-c/commit/a202b1e11ecb64ddb73a5e9f22d65e7a3651489c) #### 為什麼參數有 `char *sp` 跟 `size_t bufsize` ? - `sp` 是一個指向字串緩衝區的指標,若不為 `NULL`,則函式應將被移除元素之成員 (即 `node->value`) 複製到這個字串緩衝區。 ***用途*:** 讓使用者可以立刻取得移除節點所儲存的字串,而不需透過訪問回傳的 `element_t` 結構體取得。 ***如果 sp == NULL*:** 代表 *呼叫者* 不需要移除節點所儲存的字串,只需要刪除節點並回傳該元素的指標。 - `bufsize` 是 `sp` 的最大可用空間(包含 `\0` ),避免 **緩衝區溢位(buffer overflow)**。 ***用途*:** 確保 `strncpy()` 不會超過 `sp` 的範圍,並手動添加 `\0` 來確保字串結尾安全。 ### q_delete_mid > commit: [a8f4303](https://github.com/sysprog21/lab0-c/commit/a8f430359a1ef246afdcd8f2cfc8e94062728ca7) #### 基於下方的程式碼 `deleteMiddle` 還有沒有更好的寫法? ```clike int len = 0; struct ListNode **indirect = &head; while (*indirect) { len++; indirect = &(*indirect)->next; } int pos = len / 2; len = 0; indirect = &head; while (len != pos) { len++; indirect = &(*indirect)->next; } *indirect = (*indirect)->next; ``` 利用「快慢指標」,讓 fast 指標以兩倍速在走訪整個鏈結串列,slow 則以一倍速走訪鏈結串列,當 fast 完成走訪整個鏈結串列時,slow 指標剛好會停在中間的位置。例如: ```clike struct ListNode *fast = head; struct ListNode *slow = head; while (fast && fast->next) { fast = fast->next->next; slow = slow->next; } slow = slow->next; ``` ### q_delete_dup > commit: [a8f4303](https://github.com/sysprog21/lab0-c/commit/a8f430359a1ef246afdcd8f2cfc8e94062728ca7) 思路:用巢狀迴圈,外層迴圈用於檢測兩兩節點的值是否相同,若相同則進入第二層迴圈,在第二層迴圈中刪除重複的節點。 #### 還有改進空間嗎? 用 Hash Table ... ### q_swap > commit: [244dc15](https://github.com/sysprog21/lab0-c/commit/244dc156ad2ab4a00350e9184d3b5a046a638850) 思路:利用 `list_for_each(cur, head)` 去走訪整個鏈結串列,再透過 `list_move(cur, cur->next)`,由此一來每一輪的 `cur->next` 都會被插入到 `cur` 前面,又執行完 `list_move` 後,`cur` 無形中會自動跑到下一個節點,如下圖所示: ![截圖 2025-03-04 晚上8.40.59](https://hackmd.io/_uploads/ryBkruEiyl.png) #### 依照上述做法會出現問題,當鏈結串列長度為奇數時,會多換一次,導致最後一個節點被移到最前面,怎麼解決? 多加一個判斷式,當 cur->next == head 時,就 break迴圈,避免發生交換即可。 > commit: [8904527](https://github.com/sysprog21/lab0-c/commit/89045277c7ccbfe472c76b45c06e59e6b708563d) #### 當完成 `q_reverseK` 時,即可將 `q_swap` 寫成: ```c q_reverseK(head, 2); ``` ### q_reverse > commit: [244dc15](https://github.com/sysprog21/lab0-c/commit/244dc156ad2ab4a00350e9184d3b5a046a638850) 作法:利用 `list_for_each_safe(cur, next, head)` 去走訪整個鏈結串列,再透過 `list_move(cur, head)` 將每一個點都重新插入 `head`,即可完成 reverse 操作。 ### q_reverseK > commit: [fcd4d2c](https://github.com/sysprog21/lab0-c/commit/fcd4d2c084a539ea43d10ecd245a91b373d28a0c) 作法:先得出鏈結串列的長度 `count`,當 `count` 大於 K 時,準備 `prev` 跟 `next` 兩個輔助指標做 Reverse K-group,做完後要 `count -= k`。 ### q_descend > commit: [13ffad8](https://github.com/sysprog21/lab0-c/commit/13ffad81caea17b9c5625fb63ec21e286171380b) #### 有更好的做法,怎麼做? 我最一開始的思路是先找出串列中的最大值 `max`,再利用遞迴找出 `max->next` 子串列的最大值回傳給 `max->next`,如下程式碼所示,但會 Time out 時間複雜度為 $O(n^2)$ 等級。 用兩個指標來進行反向走訪: - `max`:追蹤目前反向走訪過程中遇到的最大值節點(初始指向 `head->prev`,即尾部節點)。 - `cur`:負責走訪 `max` 前方的節點(初始指向 `max->prev` )。 走訪過程: - 如果 `cur` 所指的值**小於或等於** `max`,則刪除該節點。 - 如果 `cur` 所指的值**大於** `max`,則更新 `max` 為 `cur`,確保 `max` 始終指向目前反向走訪過程中遇到的最大值。 - 移動 `cur` 到 `prev`,繼續走訪。 如此一來就可以有效地保留遞減序列的節點,並刪除不符合條件的節點。 ### q_ascend > commit: [13ffad8](https://github.com/sysprog21/lab0-c/commit/13ffad81caea17b9c5625fb63ec21e286171380b) > 作法與 `q_descend` 相似,一樣是透過反向走訪的思路,差別在走訪過程的條件,`q_ascend` 要刪除**大於等於** `min` 的值,若 `cur` 所指的值**小於** `min`,則更新 `min` 為 `cur`,以確保 `min` 始終指向目前反向走訪過程中遇到的最小值。如此一來就可以有效地 **保留遞增序列的節點,並刪除不符合條件的節點**。 ### q_sort > commit: [e5f1033](https://github.com/sysprog21/lab0-c/commit/e5f10336478bebd2c575aebf3f9e6d7671de41a8) 我原先參考了 [第二周測驗一 的 list_quicksort 實作](https://hackmd.io/@sysprog/linux2025-quiz2),我將其轉換成 Linux kernel 風格的鏈結串列: ```c struct list_head list_less, list_greater; element_t *pivot; element_t *item = NULL, *is = NULL; INIT_LIST_HEAD(&list_less); INIT_LIST_HEAD(&list_greater); pivot = list_first_entry(head, element_t, list); list_del(&pivot->list); if (!descend) { list_for_each_entry_safe (item, is, head, list) { if (strcmp(item->value, pivot->value) < 0) list_move_tail(&item->list, &list_less); else list_move_tail(&item->list, &list_greater); } q_sort(&list_less, false); q_sort(&list_greater, false); list_add(&pivot->list, head); list_splice(&list_less, head); list_splice_tail(&list_greater, head); } ``` #### 使用 quick sort 為什麼會超時? 因為在 Worst Case 的情況下,Quick Sort 的時間複雜度會來到 $O(n^2)$ 等級。例如在全部節點值都相同的情況下,使用 **Singely-Linked-List** 示意: 令 $n$ 表示傳入的串列長度,第一個節點當作 `pivot` ![截圖 2025-03-06 中午12.02.04](https://hackmd.io/_uploads/Hk5rCqLjJl.png) 再將剩餘的 $n-1$ 個節點做分割,比 `pivot` 小的節點插入到 `list_less` 串列中,反之則插入 `list_greater` 串列中。在此例中,當執行完 `list_for_each_entry_safe` 迴圈後,並無法分割串列,因為全部的節點都相同,因此都會被插入到 `list_greater` 中,還是得到長度為 $n-1$ 的子串列。 ![截圖 2025-03-06 中午12.10.22](https://hackmd.io/_uploads/ry54loIoye.png) 再遞迴呼叫 `q_sort`,所以都是透過設 `pivot` 在做分割串列,每回合只切一個節點,由此可推得時間函數式為 $T(n) = T(n-1) + cn$ ,其中 $c$ 為常數,$cn$ 表示執行 `list_for_each_entry_safe` 迴圈的時間,經化簡後得 $T(n) = O(n^2)$ \begin{gather} T(n) = T(n-1) + cn\\=T(n-2) + n + (n-1)\\ = T(n-3) + cn + c(n-1) + c(n-2)\\ ...... \\ = T(0)+cn+c(n-1)+c(n-2)+...+c \\ \because T(0) = 0 \\ \therefore T(n) = c[n+(n-1)+(n-2)+...+1] \\ = \frac {cn(n-1)}{2} = O(n^2)\end{gather} 故無法滿足時間複雜度 $O(nlogn)$ 的要求。 #### 如何優化? 改用 **Merge Sort** ,因為不管在什麼情況下,時間複雜度皆為 $O(nlogn)$ 等級。 > 參考 [Linked List Sort](https://npes87184.github.io/2015-09-12-linkedListSort/) 之 Merge Sort #### 這樣寫 Merge Sort,會出現 Segmentation Fault,是什麼問題? ```c=234 if (!head || list_empty(head) || list_is_singular(head)) return; struct list_head *fast = head->next; struct list_head *slow = head->next; while (fast != head && fast->next != head) { fast = fast->next->next; slow = slow->next; } LIST_HEAD(left); list_cut_position(&left, head, slow); q_sort(head, descend); q_sort(&left, descend); struct list_head *merged = mergeTwoLists(&left, head, descend); INIT_LIST_HEAD(head); list_splice(merged, head); ``` 使用 [Valgrind](https://valgrind.org) 分析,執行以下命令: ```bash $ valgrind -q --leak-check=full ./qtest -v 3 -f traces/trace-04-ops.cmd ``` 發現是 Stack Overflow ,看起來是第 248 列的遞迴沒寫好。 ``` cmd> sort ==4012223== Stack overflow in thread #1: can't grow stack to 0x1ffe801000 ==4012223== Stack overflow in thread #1: can't grow stack to 0x1ffe801000 ==4012223== Can't extend stack to 0x1ffe8010b8 during signal delivery for thread 1: ==4012223== no stack segment ==4012223== ==4012223== Process terminating with default action of signal 11 (SIGSEGV) ==4012223== Access not within mapped region at address 0x1FFE8010B8 ==4012223== Stack overflow in thread #1: can't grow stack to 0x1ffe801000 ==4012223== at 0x1104B2: q_sort (queue.c:248) ``` 先用簡單的例子來追蹤程式碼: ![截圖 2025-03-07 上午11.17.42](https://hackmd.io/_uploads/B1LwH1uiyx.png =70%x) 在執行完 while loop 後: ![截圖 2025-03-07 上午11.20.38](https://hackmd.io/_uploads/BJBGL1uoyg.png =70%x) :::warning TODO: Explain how list_cut_position(&left, head, slow) exactly do ...... > 將 `head` 到 `slow` (**包含 `slow`**) 的節點接到 `left` 上,若 `left` 不為空的話,則會將其原本的節點都覆蓋過去,即原本節點都會被 "Remove"(不會被釋放)。 ::: 在執行完 `list_cut_position(&left, head, slow);` 後: ![截圖 2025-03-07 上午11.38.59](https://hackmd.io/_uploads/B10L9kdo1e.png =70%x) 此時,再執行 `q_sort(&left, descend);` 時,會發現 `left` 就是原本的 `head`,根本沒切到,導致無限遞迴,造成 Stack Overflow ! #### 修正: 把 `list_cut_position(&left, head, slow);` 修改即可。 ```diff - list_cut_position(&left, head, slow); + list_cut_position(&left, head, slow->prev); ``` 接著,**Merge Sort** 會用到 `merge_two_lists` 輔助函數,用來合併由 `q_sort` 函數切割成兩半的子串列,比較特別的是要「 做 ascending / descending 的判斷 」。其中,為了要確保是 **Stable Sorting**,因此當遇到重複的數字時應保持原排列順序,以 ascending 為例,當目前左半子串列的值小於等於當前右半子串列的值時,應優先合併左半子串列的值。 ### q_merge > commit: [3e931f4](https://github.com/sysprog21/lab0-c/commit/3e931f4b8f70c3cac27a86e0d1667f6bf1492851) 先了解 `queue_contex_t` 結構體定義: ![截圖 2025-03-08 上午9.11.45](https://hackmd.io/_uploads/BkQDKMYiyg.png =70%x) 思路:首先,新增一個指標指向第一條 queue,作為合併的目標。接下來,將其餘所有 queue 依序合併到第一條 queue 中。我使用 `list_for_each_entry_safe` 走訪從第二條開始的每一條 queue,並透過 `merge_two_lists` 將當前走訪到的 queue 合併至第一條 queue。同時,需即時更新第一條 queue 的 size。當所有 queue 都合併完成後,回傳第一條 queue 的最終 size。 ## 如何驗證 queue.c 實作的 method? ### 在終端機輸入 make test 後,會發生什麼? 執行 [scripts/driver.py](https://github.com/sysprog21/lab0-c/blob/master/scripts/driver.py) 這個 Python 程式。快速瀏覽發現這個自動評分程式就是將 traces/trace-XX-CAT.cmd 這樣形式的命令序列提供給 `qtest.c` 內的命令直譯器,然後依據給定的檢驗標準,逐一判定該給測試者多少分數。 ## 整合網頁伺服器 ### File descriptor (fd) > [Reference 1](https://kkc.github.io/2020/08/22/file-descriptor/) > [Reference 2](https://man7.org/training/download/lusp_fileio_slides.pdf) File Descriptor 是一個非負整數,代表 File Descriptor Table 中的一個 entry 的索引。File Descriptor Table 是儲存在 PCB (即 Linux 中的 `task_struct`)中的一個資料結構,用來記錄程序所開啟檔案的相關資訊。每個 entry 會指向一個 Open File Table 的項目,該表中包含與檔案操作有關的詳細資料,例如檔案的偏移量、開啟模式等。Open File Table 中的每個項目則會對應到一個 i-node(Linux 檔案系統中的資料結構),而 i-node 中則紀錄了檔案的屬性資訊,例如檔案的路徑、大小、權限等。可以使用以下的 function 來查看當前 process 的 File Descriptor Table: ```c void list_open_fds() { char path[512]; // 增加緩衝區大小 snprintf(path, sizeof(path), "/proc/%d/fd", getpid()); DIR *dir = opendir(path); if (!dir) { perror("opendir"); return; } struct dirent *entry; printf("Open file descriptors for process %d:\n", getpid()); printf("FD\tType\t\tTarget\n"); printf("---------------------------------------------\n"); while ((entry = readdir(dir)) != NULL) { if (entry->d_name[0] == '.') continue; // Skip "." and ".." char fd_path[512], target_path[512]; // 增加緩衝區大小 int written = snprintf(fd_path, sizeof(fd_path), "/proc/%d/fd/%s", getpid(), entry->d_name); // 檢查是否發生截斷 if (written < 0 || written >= sizeof(fd_path)) { fprintf(stderr, "Path truncated: %s\n", entry->d_name); continue; } // Get the target of the symbolic link ssize_t len = readlink(fd_path, target_path, sizeof(target_path) - 1); if (len != -1) { target_path[len] = '\0'; } else { snprintf(target_path, sizeof(target_path), "Unknown"); } // Get the type of the file descriptor struct stat statbuf; if (stat(fd_path, &statbuf) == 0) { char *type; if (S_ISREG(statbuf.st_mode)) { type = "Regular File"; } else if (S_ISDIR(statbuf.st_mode)) { type = "Directory"; } else if (S_ISCHR(statbuf.st_mode)) { type = "Character Device"; } else if (S_ISBLK(statbuf.st_mode)) { type = "Block Device"; } else if (S_ISFIFO(statbuf.st_mode)) { type = "FIFO (Pipe)"; } else if (S_ISSOCK(statbuf.st_mode)) { type = "Socket"; } else { type = "Unknown"; } printf("%s\t%-15s\t%s\n", entry->d_name, type, target_path); } else { printf("%s\tUnknown\t\t%s\n", entry->d_name, target_path); } } closedir(dir); } ``` 查看建立 socket 前跟後 FD Table 的差異,以及當有新的 client 連進來後的差異: :::spoiler 完整測試程式碼 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <errno.h> #include <sys/select.h> #include <dirent.h> #include <sys/stat.h> #define SERVER_PORT 1234 #define BUF_SIZE 1024 #define MAX_CLIENTS 100 void list_open_fds() { char path[512]; // 增加緩衝區大小 snprintf(path, sizeof(path), "/proc/%d/fd", getpid()); DIR *dir = opendir(path); if (!dir) { perror("opendir"); return; } struct dirent *entry; printf("Open file descriptors for process %d:\n", getpid()); printf("FD\tType\t\tTarget\n"); printf("---------------------------------------------\n"); while ((entry = readdir(dir)) != NULL) { if (entry->d_name[0] == '.') continue; // Skip "." and ".." char fd_path[512], target_path[512]; // 增加緩衝區大小 int written = snprintf(fd_path, sizeof(fd_path), "/proc/%d/fd/%s", getpid(), entry->d_name); // 檢查是否發生截斷 if (written < 0 || written >= sizeof(fd_path)) { fprintf(stderr, "Path truncated: %s\n", entry->d_name); continue; } // Get the target of the symbolic link ssize_t len = readlink(fd_path, target_path, sizeof(target_path) - 1); if (len != -1) { target_path[len] = '\0'; } else { snprintf(target_path, sizeof(target_path), "Unknown"); } // Get the type of the file descriptor struct stat statbuf; if (stat(fd_path, &statbuf) == 0) { char *type; if (S_ISREG(statbuf.st_mode)) { type = "Regular File"; } else if (S_ISDIR(statbuf.st_mode)) { type = "Directory"; } else if (S_ISCHR(statbuf.st_mode)) { type = "Character Device"; } else if (S_ISBLK(statbuf.st_mode)) { type = "Block Device"; } else if (S_ISFIFO(statbuf.st_mode)) { type = "FIFO (Pipe)"; } else if (S_ISSOCK(statbuf.st_mode)) { type = "Socket"; } else { type = "Unknown"; } printf("%s\t%-15s\t%s\n", entry->d_name, type, target_path); } else { printf("%s\tUnknown\t\t%s\n", entry->d_name, target_path); } } closedir(dir); } typedef struct { int client_fd; char client_ip[INET_ADDRSTRLEN]; int client_port; } client_info_t; int main() { list_open_fds(); // Create a socket descriptor int server_fd; if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("Socket creation failed"); return -1; } list_open_fds(); // Eliminates "Address already in use" error from bind int opt = 1; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) != 0) { perror("Error setting SO_REUSEADDR on socket"); close(server_fd); return -1; } // Set up server address struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // Bind socket if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { perror("Bind failed"); close(server_fd); return -1; } // Listen for connections if (listen(server_fd, SOMAXCONN) < 0) { perror("Listen failed"); close(server_fd); return -1; } printf("伺服器已啟動,等待連接...\n"); // Initialize client array and file descriptor sets client_info_t clients[MAX_CLIENTS]; for (int i = 0; i < MAX_CLIENTS; i++) { clients[i].client_fd = -1; // Mark as unused } fd_set read_fds; // read_fds represent a set of file descriptors int max_fd = server_fd; // init the highest-numbered file descriptor to server_fd while (1) { // Clear and set file descriptor sets FD_ZERO(&read_fds); // Clear FD_SET(server_fd, &read_fds); // Add active client sockets to read_fds for (int i = 0; i < MAX_CLIENTS; i++) { if (clients[i].client_fd != -1) { FD_SET(clients[i].client_fd, &read_fds); if (clients[i].client_fd > max_fd) { max_fd = clients[i].client_fd; } } } // Use select to monitor sockets if (select(max_fd + 1, &read_fds, NULL, NULL, NULL) < 0) { // max_fd + 1 -> nfds perror("Select failed"); continue; } // Check for new connection if (FD_ISSET(server_fd, &read_fds)) { struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len); if (client_fd < 0) { perror("Accept failed"); continue; } // Get client socket information char client_ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN); int client_port = ntohs(client_addr.sin_port); printf("客戶端 %s:%d 已連接\n", client_ip, client_port); list_open_fds(); // Find an empty slot for the new client int i; for (i = 0; i < MAX_CLIENTS; i++) { if (clients[i].client_fd == -1) { clients[i].client_fd = client_fd; strncpy(clients[i].client_ip, client_ip, INET_ADDRSTRLEN); clients[i].client_port = client_port; break; } } if (i == MAX_CLIENTS) { printf("太多客戶端,拒絕連接\n"); close(client_fd); } else if (client_fd > max_fd) { max_fd = client_fd; } } // Check for data from clients for (int i = 0; i < MAX_CLIENTS; i++) { if (clients[i].client_fd != -1 && FD_ISSET(clients[i].client_fd, &read_fds)) { char buffer[BUF_SIZE]; ssize_t bytes_received = recv(clients[i].client_fd, buffer, BUF_SIZE - 1, 0); if (bytes_received <= 0) { if (bytes_received < 0) { perror("Connection error"); } else { printf("客戶端 %s:%d 已斷開連線\n", clients[i].client_ip, clients[i].client_port); } close(clients[i].client_fd); clients[i].client_fd = -1; continue; } buffer[bytes_received] = '\0'; printf("收到來自 %s:%d 的訊息: %s\n", clients[i].client_ip, clients[i].client_port, buffer); // Send echo message back to client if (send(clients[i].client_fd, buffer, bytes_received, 0) < 0) { perror("Connection error"); close(clients[i].client_fd); clients[i].client_fd = -1; continue; } printf("訊息已送出...\n"); } } } // Cleanup close(server_fd); printf("伺服器關閉\n"); return 0; } ``` ::: :::spoiler 預期輸出 ``` Open file descriptors for process 305949: FD Type Target --------------------------------------------- 0 Character Device /dev/pts/5 1 Character Device /dev/pts/5 2 Character Device /dev/pts/5 3 Directory /proc/305949/fd 19 Regular File /home/jimmy/.vscode-server/data/logs/20250428T121627/remoteTelemetry.log 20 Regular File /home/jimmy/.vscode-server/data/logs/20250428T121627/ptyhost.log 21 Character Device /dev/ptmx 22 Regular File /home/jimmy/.vscode-server/data/logs/20250428T121627/remoteagent.log 23 Character Device /dev/ptmx 24 Character Device /dev/ptmx Open file descriptors for process 305949: FD Type Target --------------------------------------------- 0 Character Device /dev/pts/5 1 Character Device /dev/pts/5 2 Character Device /dev/pts/5 3 Socket socket:[3259968] 4 Directory /proc/305949/fd 19 Regular File /home/jimmy/.vscode-server/data/logs/20250428T121627/remoteTelemetry.log 20 Regular File /home/jimmy/.vscode-server/data/logs/20250428T121627/ptyhost.log 21 Character Device /dev/ptmx 22 Regular File /home/jimmy/.vscode-server/data/logs/20250428T121627/remoteagent.log 23 Character Device /dev/ptmx 24 Character Device /dev/ptmx 伺服器已啟動,等待連接... 客戶端 140.116.245.209:46472 已連接 Open file descriptors for process 305949: FD Type Target --------------------------------------------- 0 Character Device /dev/pts/5 1 Character Device /dev/pts/5 2 Character Device /dev/pts/5 3 Socket socket:[3259968] 4 Socket socket:[3259974] 5 Directory /proc/305949/fd 19 Regular File /home/jimmy/.vscode-server/data/logs/20250428T121627/remoteTelemetry.log 20 Regular File /home/jimmy/.vscode-server/data/logs/20250428T121627/ptyhost.log 21 Character Device /dev/ptmx 22 Regular File /home/jimmy/.vscode-server/data/logs/20250428T121627/remoteagent.log 23 Character Device /dev/ptmx 24 Character Device /dev/ptmx ``` ::: ### select system call > [Manual page](https://man7.org/linux/man-pages/man2/select.2.html) #### 運作原理: Select 系統呼叫透過 fd_set 位元陣列來監控多個 file descriptor,檢查它們是否準備好進行讀取(readfds)、寫入(writefds)或是否有例外狀況(exceptfds),若已準備好,則使用 FD_SET() 將準備好之文件的 fd 對應到該 fd_set 的 bit 設為 1,告訴 select() 幫我監聽這個 fd 是否可讀。例如:使用 accept() 系統呼叫得到一個 socket_fd 假設是 4,那麼因為 socket_fd 是要接收客戶端的訊息,所以 readfds 的第 4 個 bit 就設為 1,也就是告訴 select() 幫我監聽這個 fd 是否可讀,若沒有 client 連線,則 select() 會 block 程式,直到有 fd 就緒可讀或 Timeout。另外,**當 select() return 後,會將 readfds 中除了可讀 fd bit 之外的所有 bit 清 0**。此外 fd_set 的 size 由 nfds 記錄,表示目前共監聽幾個 file descriptors,而監聽的上限則由巨集 FD_SETSIZE 限制。另外,我透過以下方式來查看 fd_set 的狀態: ```c void print_fd_set(fd_set *fds, int max_fd) { printf("fd_set contents (up to %d):\n", max_fd); for (int i = 0; i <= max_fd; i++) { if (FD_ISSET(i, fds)) { printf("fd %d: 1, ", i); } else { printf("fd %d: 0, ", i); } } } ``` 接著,寫程式來實驗看看 Select: :::spoiler server 完整測試程式碼 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <errno.h> #include <sys/select.h> #define SERVER_PORT 1234 #define BUF_SIZE 1024 #define MAX_CLIENTS 100 void print_fd_set(fd_set *fds, int max_fd) { printf("fd_set contents (up to %d):\n", max_fd); for (int i = 0; i <= max_fd; i++) { if (FD_ISSET(i, fds)) { printf("fd %d: 1, ", i); } else { printf("fd %d: 0, ", i); } } puts(""); printf("-----------------------------------------------------------------\n"); } typedef struct { int fd; char ip[INET_ADDRSTRLEN]; int port; } client_info_t; int main() { // Create a socket descriptor int server_fd; if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("Socket creation failed"); return -1; } // Eliminates "Address already in use" error from bind int opt = 1; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) != 0) { perror("Error setting SO_REUSEADDR on socket"); close(server_fd); return -1; } // Set up server address struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // Bind socket if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { perror("Bind failed"); close(server_fd); return -1; } // Listen for connections if (listen(server_fd, SOMAXCONN) < 0) { perror("Listen failed"); close(server_fd); return -1; } printf("伺服器已啟動,等待連接...\n"); // Initialize client array and file descriptor sets client_info_t clients[MAX_CLIENTS]; for (int i = 0; i < MAX_CLIENTS; i++) { clients[i].fd = -1; // Mark as unused } fd_set read_fds; // read_fds represent a set of file descriptors int max_fd = server_fd; // init the highest-numbered file descriptor to server_fd printf("Init read_fds:\n"); print_fd_set(&read_fds, max_fd); while (1) { // Clear and set file descriptor sets FD_ZERO(&read_fds); // Clear printf("After execute FD_ZERO:\n"); print_fd_set(&read_fds, max_fd); FD_SET(server_fd, &read_fds); // pull up the server_fd(index) bit in read_fds printf("After execute FD_SET with server_fd:\n"); print_fd_set(&read_fds, max_fd); // Add active client sockets to read_fds for (int i = 0; i < MAX_CLIENTS; i++) { if (clients[i].fd != -1) { FD_SET(clients[i].fd, &read_fds); if (clients[i].fd > max_fd) { max_fd = clients[i].fd; } } } printf("After execute FD_SET with client_fd:\n"); print_fd_set(&read_fds, max_fd); // Use select to monitor sockets if (select(max_fd + 1, &read_fds, NULL, NULL, NULL) < 0) { // max_fd + 1 -> nfds perror("Select failed"); continue; } printf("After call select:\n"); print_fd_set(&read_fds, max_fd); // Check for new connection if (FD_ISSET(server_fd, &read_fds)) { struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len); if (client_fd < 0) { perror("Accept failed"); continue; } printf("After accept client connection:\n"); print_fd_set(&read_fds, max_fd); // Get client socket information char client_ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN); int client_port = ntohs(client_addr.sin_port); printf("客戶端 %s:%d 已連接\n", client_ip, client_port); // Find an empty slot for the new client int i; for (i = 0; i < MAX_CLIENTS; i++) { if (clients[i].fd == -1) { clients[i].fd = client_fd; strncpy(clients[i].ip, client_ip, INET_ADDRSTRLEN); clients[i].port = client_port; break; } } if (i == MAX_CLIENTS) { printf("太多客戶端,拒絕連接\n"); close(client_fd); } else if (client_fd > max_fd) { max_fd = client_fd; } } // Check for data from clients for (int i = 0; i < MAX_CLIENTS; i++) { if (clients[i].fd != -1 && FD_ISSET(clients[i].fd, &read_fds)) { char buffer[BUF_SIZE]; ssize_t bytes_received = recv(clients[i].fd, buffer, BUF_SIZE - 1, 0); if (bytes_received <= 0) { if (bytes_received < 0) { perror("Connection error"); } else { printf("客戶端 %s:%d 已斷開連線\n", clients[i].ip, clients[i].port); } close(clients[i].fd); clients[i].fd = -1; continue; } buffer[bytes_received] = '\0'; printf("收到來自 %s:%d 的訊息: %s\n", clients[i].ip, clients[i].port, buffer); // Send echo message back to client if (send(clients[i].fd, buffer, bytes_received, 0) < 0) { perror("Connection error"); close(clients[i].fd); clients[i].fd = -1; continue; } printf("訊息已送出...\n"); print_fd_set(&read_fds, max_fd); } } } // Cleanup close(server_fd); printf("伺服器關閉\n"); return 0; } ``` ::: ::: spoiler client 完整測試程式碼 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <errno.h> #define CONNECT_HOST "140.116.245.209" #define CONNECT_PORT 1234 #define BUF_SIZE 1024 int main() { // Create socket int client_fd = socket(AF_INET, SOCK_STREAM, 0); if (client_fd < 0) { perror("Socket creation failed"); return 1; } // Set up server address struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); // Initialize server_addr structure to zero server_addr.sin_family = AF_INET; server_addr.sin_port = htons(CONNECT_PORT); // Convert the CONNECT_HOST from string to binary if (inet_pton(AF_INET, CONNECT_HOST, &server_addr.sin_addr) <= 0) { perror("Invalid address"); close(client_fd); return 1; } // Connect to server if (connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { perror("Connection failed"); close(client_fd); return 1; } // Get local address and port struct sockaddr_in local_addr; socklen_t addr_len = sizeof(local_addr); if (getsockname(client_fd, (struct sockaddr*) &local_addr, &addr_len) < 0) { perror("Getsockname failed"); close(client_fd); return 1; } // Get local address char local_ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &local_addr.sin_addr, local_ip, INET_ADDRSTRLEN); int local_port = ntohs(local_addr.sin_port); // ntohs: Network to Host Short (16-bits integer) printf("本機: %s:%d\n", local_ip, local_port); while(1) { // Get input char input[BUF_SIZE]; printf("輸入訊息: "); if (fgets(input, BUF_SIZE, stdin) && input[0] == '\n') { printf("輸入的訊息不得為空\n"); continue; } // Remove newline character("\n") from input input[strcspn(input, "\n")] = 0; // Send echo message back to client if (send(client_fd, input, strlen(input), 0) < 0) { perror("Send failed"); close(client_fd); return 1; } printf("訊息已傳送...\n"); // Receive response char buffer[BUF_SIZE]; // To store recevied message ssize_t bytes_received = recv(client_fd, buffer, BUF_SIZE - 1, 0); if (bytes_received < 0) { perror("Receive failed"); break; } else if (bytes_received == 0) { printf("伺服器已斷開連線\n"); break; } else { buffer[bytes_received] = '\0'; printf("收到訊息: %s\n", buffer); } } // Cleanup close(client_fd); printf("客戶端已中止...\n"); return 0; } ``` ::: \ 預期輸出: - 在尚未有客戶端連線時,可以看到 `read_fds` 中 `fd 3` 被設為 1,而程式 block 在 select(),因為還尚未有 fd 可讀。 ```bash jimmy@NEAT:~/linux2025/socket_exam$ ./server_select 伺服器已啟動,等待連接... Init read_fds: fd_set contents (up to 3): fd 0: 0, fd 1: 0, fd 2: 0, fd 3: 1, ----------------------------------------------------------------- After execute FD_ZERO: fd_set contents (up to 3): fd 0: 0, fd 1: 0, fd 2: 0, fd 3: 0, ----------------------------------------------------------------- After execute FD_SET with server_fd: fd_set contents (up to 3): fd 0: 0, fd 1: 0, fd 2: 0, fd 3: 1, ----------------------------------------------------------------- After execute FD_SET with client_fd: fd_set contents (up to 3): fd 0: 0, fd 1: 0, fd 2: 0, fd 3: 1, ----------------------------------------------------------------- ``` :::info 為什麼在尚未有客戶端連線時,server_fd 是不可讀的? > server_fd 是監聽狀態的 socket,因此,在當有新的 client 執行 connect() 後,也就是有一個 connection 在等待被 accept() 時,監聽 socket 才可讀。 ::: - 在當有客戶端連線時,可以發現多了以下的輸出內容,首先是 select 發現 `fd 3` 可讀了(因為有新的連線),當新的連線進來後,因為新連線的 fd 值會大於 max_fd,因此需將 max_fd 更新為最新連線的 fd,接著就重複程式中迴圈的流程(參考上述 server 完整測試程式碼)。 ```bash After call select: fd_set contents (up to 3): fd 0: 0, fd 1: 0, fd 2: 0, fd 3: 1, ----------------------------------------------------------------- After accept client connection: fd_set contents (up to 3): fd 0: 0, fd 1: 0, fd 2: 0, fd 3: 1, ----------------------------------------------------------------- 客戶端 140.116.245.209:36360 已連接 After execute FD_ZERO: fd_set contents (up to 4): fd 0: 0, fd 1: 0, fd 2: 0, fd 3: 0, fd 4: 0, ----------------------------------------------------------------- After execute FD_SET with server_fd: fd_set contents (up to 4): fd 0: 0, fd 1: 0, fd 2: 0, fd 3: 1, fd 4: 0, ----------------------------------------------------------------- After execute FD_SET with client_fd: fd_set contents (up to 4): fd 0: 0, fd 1: 0, fd 2: 0, fd 3: 1, fd 4: 1, ----------------------------------------------------------------- ``` 註:由於 select 能監視的 file descriptor 有限(被巨集 FD_SETSIZE 限制,通常為 1024),故現在需支援大量服務的系統不再使用,改用 poll(2) 或 epoll(7) 替代。 ## shuffle 實作 ### q_shuffle > commit: [484b7ea](https://github.com/sysprog21/lab0-c/commit/484b7ea9ed552612c31aacd5081eb9da5ef354a7) 參考 [你所不知道的 C 語言:Fisher–Yates shuffle](https://hackmd.io/@sysprog/c-linked-list#Fisher–Yates-shuffle) 演算法實作,透過 Pencil-and-paper method 對 Linux Kernel 的環狀雙向鏈結串列進行隨機洗牌,每次隨機選取一個節點並將其移動到 `new` 串列,直到所有節點都被移動。 ### do_shuffle > commit: [484b7ea](https://github.com/sysprog21/lab0-c/commit/484b7ea9ed552612c31aacd5081eb9da5ef354a7) 在 `qtest.c` 中新增 `do_shuffle` 函數,參考 `do_swap` 寫法,將 `q_swap(current->q);` 改成 `q_shuffle(current->q);`。詳細流程我還要弄清楚。 ### 分析 q_shuffle 是否 Uniform distribution 透過統計方法驗證 shuffle,利用作業提供的測試檔測試,結果如下: ```bash $ python3 test_shuffle.py Expectation: 41666 Observation: {'1234': 70538, '1243': 5611, '1324': 33258, '1342': 13650, '1423': 15627, '1432': 5872, '2134': 79863, '2143': 82082, '2314': 50809, '2341': 31226, '2413': 17591, '2431': 54725, '3124': 25432, '3142': 66487, '3214': 52806, '3241': 7783, '3412': 39009, '3421': 62571, '4123': 46892, '4132': 35171, '4213': 85991, '4231': 7836, '4312': 58659, '4321': 50511} chi square sum: 363008.4137186195 ``` 測試出來不太均勻,需要調整。 ### 解決方法 > commit: [e28bbdd](https://github.com/JimmyChongz/lab0-c/commit/e28bbdd2500f58dcfbe9b880eb7b6e7e4a47d041) 後來,我發現是程式碼中使用 `srand(time(NULL))` 的問題,因為這個函數會根據時間(1970 年 1 月 1 日到現在的秒數)初始化亂數種子,由於 `time(NULL)` 的精度只有秒,當在短時間內重複呼叫 `q_shuffle()` 時,其中的 `srand(time(NULL))` 會產生相同的亂數種子,導致每次產生的亂數「序列」相同。 **驗證:在短時間內重複呼叫 `srand(time(NULL))` 會產生相同的亂數種子,且每次產生的亂數「序列」相同。** :::spoiler 完整驗證程式碼 ```c #include <stdio.h> #include <time.h> #include <stdlib.h> void test() { srand(time(NULL)); printf("random seed: %lu\n", time(NULL)); for (int i = 0; i < 5; i++) { printf("%d ", rand()); } printf("\n"); } int main() { int count = 5; while (count-- > 0) { test(); } return 0; } ``` ::: :::spoiler 預期輸出(亂數種子相同、序列相同) ``` random seed: 1745983409 1042424400 1453791513 1018818495 61690579 736732418 random seed: 1745983409 1042424400 1453791513 1018818495 61690579 736732418 random seed: 1745983409 1042424400 1453791513 1018818495 61690579 736732418 random seed: 1745983409 1042424400 1453791513 1018818495 61690579 736732418 random seed: 1745983409 1042424400 1453791513 1018818495 61690579 736732418 ``` ::: 因此,當在執行 `test_shuffle.py` 時,在 1,000,000 次的洗牌中,會有部分的洗牌順序相同,導致不均勻洗牌。 再次測試: ```bash $ python3 test_shuffle.py Expectation: 41666 Observation: {'1234': 41430, '1243': 41914, '1324': 41566, '1342': 41802, '1423': 41328, '1432': 41663, '2134': 41660, '2143': 41646, '2314': 41720, '2341': 41667, '2413': 41614, '2431': 41952, '3124': 41714, '3142': 41739, '3214': 41585, '3241': 41503, '3412': 41947, '3421': 41556, '4123': 41554, '4132': 41802, '4213': 41511, '4231': 41509, '4312': 41626, '4321': 41992} chi square sum: 16.01344021504344 ``` ![image](https://hackmd.io/_uploads/SJgTrrkelg.png =65%x) 可以發現,1234 的各種排列組合出現的次數皆為四萬多次,均勻多了。 ## 讀論文 〈[Dude, is my code constant time?](https://eprint.iacr.org/2016/1123.pdf)〉 > 參考作業提供的 [論文重點提示](https://hackmd.io/@sysprog/linux2025-lab0-d#-論文%E3%80%88Dude-is-my-code-constant-time〉重點提示) 從引言的敘述中,推測 **Timing Leakage** 指的是程式執行時間與秘密數據之間的關聯性,導致攻擊者可以透過測量加密實作的執行時間來推測出機密資訊,例如:在 1996 年 Paul Kocher 發現 RSA 私鑰解密的運算執行時間取決於私鑰位元值,所以攻擊者就可以透過測量解密運算的執行時間來逐步恢復私鑰。這也是為什麼要探討 is my code constant time 的重要原因,以確保程式碼在不同輸入條件下的執行時間是固定的,以減少被 side-channel attacks 的可能性。 ## 參考論文提供的 [GitHub](https://github.com/oreparaz/dudect) 來修改 > commit: [42af940](https://github.com/JimmyChongz/lab0-c/commit/42af94069697c218a80297bed43e25bcb4ef464d) ### Macro 中的 `##` 是什麼? 參考規格書對 ## operator 的敘述: ![截圖 2025-03-15 下午1.03.50](https://hackmd.io/_uploads/SJvrctMh1l.png) 得知 ## 是在巨集中的「token 連接符號」,在 C 的預處理器中,## 可以將兩個 token 合併成一個 token,當參數為空時,C 預處理器會插入 placemarker,它最終會被刪除,不會影響程式碼的執行結果,例如: ```c #include <stdio.h> #define A(x) Value_##x #define B(x, y) x##y int main(int argc, const char * argv[]) { int A(1) = 9; int A(a) = 21; printf("%d, %d\n", Value_1, Value_a); int B(a, 1) = 21; int B(b, 1) = 9; printf("%d, %d\n", a1, b1); printf("%d\n", B(123, )); // 空參數由 placemarker 代替 } ``` ``` //output 9, 21 21, 9 123 Program ended with exit code: 0 ```

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully